Java中的双重检查锁(double checked locking)分析

Java中的双重检查锁(double checked locking)分析

双重检查锁从执行效率来看,实现了效率的优化,避免了在首层判断就加上Synchorzied同步锁,导致锁的粒度过大,代来效率的低下;
另外,为了必免jvm在指令优化时,对创建对象(new)过程出现的指令重排序现象,需要组引用对象用volatile修饰

下面我们正式开始分析

相信,我们在解决并发获得单例时,第一想到的就在getInstance方法上加上同步锁。
代码如下:

public class Singleton {
private static Singleton uniqueSingleton;

private Singleton() {
}

public synchronized Singleton getInstance() {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
} 
}    

但往往,每个线程在getInstance方法时,都要拿到同步锁,会使得系统开销增大,有点没必要。

优化方案
—当已经有一个实例后,通过双重判断,可以使得以后的线程在调用getInstance方法不需要获得同步锁,效率优化。

public class Singleton {
private static Singleton uniqueSingleton;

private Singleton() {
}

public Singleton getInstance() {
if (null == uniqueSingleton) {
synchronized (Singleton.class) {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();   // error
}
}
}
return uniqueSingleton;
}
}   

但还有一个问题,在有些情况下,通过这种方式拿到的Singleton对象,可能是错误的 。

为什么呢?

回顾我们new对象的3个步骤

① 分配内存空间

② 初始化对象

③ 将对象指向刚分配的内存空间

但jvm在指令优化时,会出现步骤②和③对调的情况,比如线程1在经过俩层为null判断后,进入new的动作,在还没有初始化对象时,就返加了地址值,线程2在第一个为null判断时,因为对象已经不为空,那么就直接返回了对象。

这时,就会出现问题。
(虽然我没遇到过此问题,也不清楚这种重排序在实际应用中会导致什么,但大佬都很怕这个问题)

那么,怎么解决呢? 在引用对象前加上volatile关键字,可以解决jvm重排序问题

代码如下

public class Singleton {
private volatile static Singleton uniqueSingleton;

private Singleton() {
}

public Singleton getInstance() {
if (null == uniqueSingleton) {
synchronized (Singleton.class) {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
}
}
return uniqueSingleton;
}
}   

使用volatile关键词主要可以保证代码的执行顺序不受jvm重排序影响,所以就上面的问题,对象肯定会在初始化后才会返回内存的地址值。

有关volatite的更多介绍,需要借助jvm内存模型,可以参考本人转载文章volatile关键字解析

End!


Java中的双重检查锁(double checked locking)分析