单例模式的双检锁写法
public class DoubleCheckedLock {
private static DoubleCheckedLock instance;
public static DoubleCheckedLock getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLock.class) {
if(instance==null){
instance=new DoubleCheckedLock();
}
}
}
return instance;
}
}
双检锁写法的问题
双检锁机制确实解决了多线程并行中并不会出现重复new对象,并且实现了懒加载, 但是由于 编译器中的优化: 指令重排序,导致了instance=new DoubleCheckedLock();这行代码在编译后的不确定性,比如,某一种编译器编译后该代码的步骤为(指令重排序后的步骤):
如果有如下的场景:线程A和线程B调用getInstance,线程A先进入,在执行到步骤1的时候被强行终止了。然后B进入,B看到的是instance已经不是null了(内存已经分配并且将地址赋值给了instance),于是就直接返回了instance,而此时的instance返回的是未经初始化的对象,instance中的字段都是缺省值,直接使用会造成错误。1. instance = 给新的实例分配内存2. 调用Singleton的构造函数来初始化instance的成员变量当然也会有其他的编译器编译后的步骤的代码是这样的(正常步骤):
使用volatile来禁止指令重排序优化可以吗?如果编译后的步骤是这样的话双检锁写法就没有问题了,但是真正的问题是根本无法知道编译器具体的编译到底是如何做的。这才是最蛋疼的地方。即不能保证双检锁写法在所有的编译器上都有正确的结果1. temp = 分配内存2. 调用temp的构造函数3. instance = temp
即使用volatile来修饰instance变量来禁止指令重排序
public class DoubleCheckedLock {
private static volatile DoubleCheckedLock instance;
public static DoubleCheckedLock getInstance() {
if (instance == null) {
synchronized (DoubleCheckedLock.class) {
if(instance==null){
instance=new DoubleCheckedLock();
}
}
}
return instance;
}
}
但是并不是所有的编译器都很好的实现了volatile禁止指令重排序的语义,volatile屏蔽指令重排序的语义在JDK 1.5才被完全的修复,此前的JDK中即使将变量声明为volatile也仍然不能完全避免重排序所导致的问题。由于这一点的原因,在JDK 1.5之前的Java中无法安全的使用DCL(双检锁)来实现单例模式
那么如何实现线程安全的Singleton呢?
1. 利用JVM的类加载和静态变量初始化特征来创建Singleton实例
class DoubleCheckedLock {
private static DoubleCheckedLock instance = new DoubleCheckedLock();
private DoubleCheckedLock() {
}
public DoubleCheckedLock getInstance() {
return instance;
}
}
因为static字段在类加载时进行初始化, 所以在加载该类时创建该类的实例并赋给instance字段, 并且classLoader将会保证直到类被完全加载并被初始化时才会可见。2. 利用在方法上添加synchronized关键字来实现线程安全的单例模式
class DoubleCheckedLock {
private static DoubleCheckedLock instance = null;
private DoubleCheckedLock() {
}
public synchronized DoubleCheckedLock getInstance() {
if (instance == null)
instance = new DoubleCheckedLock();
return instance;
}
}
虽然这种方式保证了线程的安全,但是在性能上该方法是比较低的,原因在于对整个getInstance()方法都是同步的,限定了访问的速度。3. 最优方式:使用静态内部类来实现线程安全的单例模式
class StaticSingleton{
private StaticSingleton() {
}
private static class SingletonHolder{
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return SingletonHolder.instance;
}
}
当StaticSingleton被加载时,其内部类并不会被初始化,所以实例也不会被初始化。而当getInstance方法被调用时,才会加载SingletonHolder,从而初始化了instance。同时, 由于实例的建立是在类加载时完成的,天生对多线程友好,所以getInstance方法不需要使用同步关键字(因为类加载自身处理了多线程环境下的同步问题)。