1.问题描述
在一个系统运行期间,某个类只需要一个实例运行就可以,该如何实现呢?
2.模式定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
3.解决思路
控制一个类只创建一个实例,首先就是要把类创建的权限收回,让类负责自己实例的创建,然后再提供外部访问实例的方法。代码如下所示:
public class SingleInstance{
private static SingleInstance instance;
private SingleInstance(){}
private static SingleInstance getInstance(){
if(instance == null)
instance = new SingleInstance();
return instance;
}
......
}
4.线程安全
上述代码创建了可延迟初始化的单例,然而在高并发的环境中,getInstance()方法返回了多个指向不同的实例。以下给出两个线程并发访问getInstance()方法时的一种情况:
Thread1 | Thread2 |
如果这两个线程按照上述步骤执行,当还没有创建单例对象,Thread1和Thread2都会进入创建单例实例的代码块分别创建实例。此时,Thread1创建了一个实例对象,但是Thread2此刻已无法知道,于是继续创建一个新的单例对象,导致这两个线程持有的实例并非同一个。
为解决这个问题,给此方法添加synchronized关键字,代码如下:
public class ThreadSafeSingleInstance{
public static synchronized ThreadSafeSingleInstance getInstance(){
if(instance == null)
instance = new ThreadSafeSingleInstance();
return instance;
}
}
虽然通过synchronized实现了多线程的安全访问,但是在多线程高并发的情况下,会使得性能大不如前。经分析,不难发现,使用synchronized对整个getInstance()方法进行同步是没有必要的,我们只要保证实例化这个对象的那段逻辑被一个线程执行就可以了,而返回引用的那段代码是没必要同步的。实现如下:
public DoubleCheckSingleInstance{
private volatile static DoubleCheckSingleInstance instance = nul;
//构造函数
public static DoubleCheckSingleInstance getInstance(){
if(instance == null){
synchronized(DoubleCheckSingleInstance.class){
if(instance == null)
instance = new DoubleCheckSingleInstance();
}
}
return instance;
}
}
代码注解:
在getInstance()方法里,首先判断此实例是否已经被创建了,如果还没有创建,使用synchronized同步实例化代码块。在同步代码块里,还需要再次检查是否已经创建了此类的实例,如没有第二次检查,当有两个线程同时进入该方法,同样会出现生成两个实例对象。
5.场景假设
假设有两个线程,Thread1和Thread2,它们执行以下步骤:
(1)Thread1发现instance没有被实例化,它获得锁,并去实例化此对象,JVM容许在没有完全实例化完成时,instance变量就指向此实例,因为这些步骤可以是次序颠倒的,此时instance == null为false
(2)在初始化完成之前,Thread2进入此方法,发现instance已经不为null了,Thread2便认为该实例已经初始化完成了,使用这个未完成初始化的实例对象,则很可能引起系统的崩溃。
如要设计使用线程安全的延迟的单例初始化,可使用Initialization on demand holder模式。代码如下:
public class LazyLoadedSingleInstance{
private LazyLoadedSingleInstance(){}
private static class LazyHolder{
private static final LazyLoadedSingleInstance singletonInstance = new LazyLoadedSingleInstance();
}
public static LazyLoadedSingleInstance getInstance(){
return LazyHolder.singletonInstance;
}
}
代码注解:
当JVM加载LazyLoadedSingleInstance类时,由于该类没有static属性,所以加完完成后便即可返回。只有第一次调用getInstance()方法时,JVM才会加载LazyHolder类,由于它包含一个static属性singletonInstance,所以会首先初始化这个变量,此过程并不会出现并发问题,这样即实现了一个既保证线程安全又支持延迟加载的单例模式。
6.应用场景
如果要保证系统里一个类最多只能存在一个实例时,我们就需要单例模式。例如缓存池、数据库连接池、线程池,一些应用服务实例等。