一.了解双检锁
双检锁,"Double-checked locking",简写为DCL.经常用于单例模式中.格式常如下:
// Single threaded version
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
helper = new Helper();
return helper;
}
// other functions and members...
}
对我个人而言,理解双检锁失效有两个重点:
- 编译器的写操作重排问题.
例 : B b = new B();
上面这一句并不是原子性的操作,实际上有如下两个操作
①new一个B对象
②将new出来的对象赋值给b.
直觉来说我们可能认为是先构造对象①再赋值②。但是实际上,这个顺序并不是固定的.在编译器的重排作用下,可能会出现先赋值②再构造对象①的情况.
2.结合上下文,结合使用情景.
理解了1中的写操作重排以后,我卡住了一下.因为我真不知道这种重排到底会带来什么影响.实际上是因为我看代码看的不够仔细,没有意识到使用场景。双检锁的一种常见使用场景就是在单例模式下初始化一个单例并返回,然后调用初始化方法的方法体内使用初始化完成的单例对象.
还是举个例子说:
if(b == null){
synchronized(b){
if(b == null){
b =new B();
}
}
}
return b;
两个线程:线程1和线程2.
最开始,b满足为null的条件,线程1,2都进入了外层的if中,假设说线程1抢先进入到同步块中,然后对b先赋值(注意此时还没有构造,即只执行了②,没有执行①)。
同一时刻,对于线程2而言,因为b已经被赋值,已经不满足外层的“if(b == null)”条件,它就会跳出双检锁,直接向下执行到 "return b"。
此时返回的b是一个并没有被构造(只执行了②,没有执行①)的对象,如果在调用方法中使用这个b肯定会出现NullPointer这样的问题。
所以说问题的核心还是编译器写操作,针对这个情况,java5中新增volatile关键字可以应对.
二.应对双检锁
在类中加入静态块, 执行到这个类的最初就把对象构建完成
三.旁言
1.关联:单例模式,volatile关键字,多线程
2.多讨论吧,之前一个人看,就被卡住了,结果和几个同事讨论的一下,就感觉明白多了
四.引用
1.The "Double-Checked Locking is Broken" Declarationhttps://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
2.有关“双重检查锁定失效”的说明