Java:双重检查锁定模式
2020-02-23
首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)
用来减少并发系统中竞争和同步的开销
场景 1
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
......
}
单线程,按顺序执行,没有任何问题
多线程并发时:if (helper == null)
同时满足时,不同线程都会执行new Helper()
创建新的Helper实例,可能会导致某个线程获取到的实例不完整
解决办法:加锁synchronized
场景 2
class Foo {
private Helper helper = null;
public synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
......
}
多线程并发,加锁后按顺序执行,没有任何问题
问题:每次访问都获取和释放锁,资源开销大,影响性能
解决办法:先检查实例初始化情况,如果已初始化则直接返回实例,如果没有初始化再进入锁环节
场景 3
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
......
}
根据Java内存模型, 每个线程有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存
问题:可能会存在线程1初始化了Helper实例,但是线程2并会及时获取到实例信息,导致再进入锁等待环节
解决办法:volatile关键字,被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象
场景 4
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
Helper result = helper;
if (result == null) {
synchronized(this) {
result = helper;
if (result == null) {
helper = result = new Helper();
}
}
}
return result;
}
......
}
局部变量result的使用看起来是不必要的。对于某些版本的Java虚拟机,这会使代码提速25%,而对其他的版本则无关痛痒
参考: