目录
1.什么是单例模式:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
2.单例模式存在的原因:
一些对象的存在只需要唯一的一个,例如,缓存池和线程池。如果线程池存在多例的话,会导致资
源使用过量,缓存多个的话,会导致数据不一致
3.单例模式的优缺点:
优点:
- 在内存中只有一个对象,节省内存空间
- 避免频繁的创建销毁对象,可以提高性能
- 避免对共享资源的多重占用,简化访问
- 为整个系统提供一个全局访问点
缺点:
- 没有接口,不能继承,
- 与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
4.创建方式:
创建一个
maven
项目
singleton
1. 单线程单例模式立即创建(饿汉式):
创建类cn.xs
.singleton.Hunchback
public class Hunchback {
/* 创建唯一一个单例对象 */
private static Hunchback singleton = new Hunchback();
/**
* 不能让外界直接创建对象,所以设置私有构造器
*/
private Hunchback() {
}
/**
* 单例对象的全局访问点
*
* @return
*/
public static Hunchback getInstance() {
return singleton;
}
}
2. 单线程单例模式延迟创建(懒汉式):
在创建类 cn.xs
.singleton.Lazybones
public class Lazybones {
/* 创建唯一一个单例对象 */
private static Lazybones singleton = null;
/**
* 不能让外界直接创建对象,所以设置私有构造器
*/
private Lazybones() {
}
/**
* 单例对象的全局访问点
*
* @return
*/
public static Lazybones getInstance() {
if (singleton == null) {
singleton = new Lazybones();
}
return singleton;
}
}
5.多线程下测试两种模式存在哪些问题
新建测试类
cn.xs.singleton.SingletonTest
1.先测试饿汉式,代码如下:
public class SingletonTest extends Thread {
@Override
public void run() {
Hunchback singleton = Hunchback.getInstance();
System.out.println(singleton);
}
/**
* 测试方法
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
SingletonTest singletonTest = new SingletonTest();
singletonTest.start();
}
}
}
输出的都是同一个对象,没有线程安全问题,但是不能延迟加载
2.测试懒汉式,修改 run 方法创建对象
@Override
public void run() {
Lazybones singleton = Lazybones.getInstance();
System.out.println(singleton);
}
控制台输出了两个对象,可见,懒汉式存在线程安全问题
解决方式一:
在方法上加上
synchronized
,保证对临界资源的同步互斥访问
/**
* 单例对象的全局访问点
*
* @return
*/
public synchronized static Lazybones getInstance() {
if (singleton == null) {
singleton = new Lazybones();
}
return singleton;
}
这种方式虽然解决了问题,但是当有线程在执行方法时,不管有没有创建对象,其他线程都会在外等候里面的线程执行完毕,有没有一种方式可以解决这个问题,提高代码执行效率
解决方式二:
在方法中创建对象部分设置同步块
public static Lazybones getInstance() {
if (singleton == null) {
synchronized (Lazybones.class) {
if (singleton == null) {
singleton = new Lazybones();
}
}
}
return singleton;
}
分析:当还没有创建对象的时候,如果被多个线程同时进入方法执行,比如线程
1
,线程
2
进来执行方法,两个线程同时判断外层 if
时,都为
true
,都进入外层
if
执行,当走到同步块时,比如线程
1
进入同步块执行代码,线程 2
只能在外面等着,当线程
1
创建完对象执行完同步块后,线程
2
再进入同步块执行代码,在同步块中,线程 2
判断内层
if
,结果为
false
,不用再创建对象,再有别的线程来执行方法,就不会再一直等待前一个线程了,因为外层 if
已经永远为
false
。
6.两种模式简单对比:
饿汉式 :线程安全,调用率高,但是不能延迟加载懒汉式 :线程安全,调用率不高,可以延迟加载
7.其他的单例模式:
双重检测锁式
(由于
JVM
底层内部模型原因,偶尔会出问题,不建议使用)
静态内部类式
(线程安全,调用效率高。但是,可以延时加载)
枚举式
(线程安全,调用率高,不能延时加载)
对比五种模式,如何选用?单例对象占用资源少,不需要延迟加载,枚举式比饿汉式好单例对象占用资源大,需要延迟加载,静态内部类式比懒汉式好