深究双重锁检查创建单例对象
首先我们来看一段创建单例模式的代码
public class Singleton {
static Singleton instance;
static Singleton getInstance(){
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
getInstance()方法逻辑: 首先判断instance是否为null,如果是null 则加锁,再次判断instance 是否为null,如果不为null 则创建一个实例。
设想,有两个线程同时访问getInstance()方法,判断instance都为null,进入争抢锁的阶段,然后A获取到了锁,B等待释放A锁,A 获取到锁,判断instance还是null,则开始创建实例,然后A释放锁,唤醒B,B获取到锁,判断instance 不为null,直接返回instance。看上去是不是完美,毫无破绽。
![image.png](https://upload-images.jianshu.io/upload_images/17023099-7246627eb097d15c.png&originHeight=1857&originWidth=1259&size=128423&status=done&width=719?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
image.png
然而现实,以上代码确实错误的。
上述代码,其实逻辑上没有问题,但是忽略了重排序的影响。
instance = new Singleton();
这个地方有三个操作
- 分配一块内存M
- 在M上初始化Singleton对象
- 然后将M的地址赋值给instance
假如,这一切都是顺序放生则不会有任何问题,实际上这里可以被重排序,顺序如下
- 分配一块内存M
- 然后将M的地址赋值给instance
- 在M上初始化Singleton对象
如果 在执行 2 的时候,cpu 时间片切换到其他线程上。则会出现问题。
线程进来第一步,就判断instance是否为null,否(已经指向内存地址,实际上对应地址没有初始化对象),则返回对象,这个时候内存M上还没有初始化Singleton对象。则造成NPE
所以需使用volatile禁止重排序。
正确创建方式如下
public class Singleton {
static volatile Singleton instance;
static Singleton getInstance(){
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
原文: 深究双重锁检查创建单例对象
作者:gofun成都技术中心-何刚