在Java中的双重检查锁定中的volatile
在Java中的双重检查锁定中的volatile
据我所了解,这是Java中双重检查锁定模式(自Java 5以来)的正确实现:\n
class Foo { private volatile Bar _barInstance; public Bar getBar() { if (_barInstance == null) { synchronized(this) { // or synchronized(someLock) if (_barInstance == null) { Bar newInstance = new Bar(); // 可能的额外初始化 _barInstance = newInstance; } } } return _barInstance; } }
\n我想知道,如果没有使用`volatile`,是一个严重的错误还是仅仅是一个轻微的缺陷,假设`_barInstance`只通过`getBar`访问。\n我的观点是:`synchronized`引入了happens-before关系。初始化`_barInstance`的线程在离开同步块之前将其值写入主内存。因此,即使它没有使用`volatile`,也不会出现对`_barInstance`的重复初始化:其他线程在它们的本地副本中具有`null`(在第一次检查时返回`true`),但是在进入同步块后必须从主内存中读取新值(在第二次检查时返回`false`并且不进行重新初始化)。因此,唯一的问题是每个线程都会获取多余的一次锁。\n据我所了解,这在CLR中是正确的,我相信在JVM中也是正确的。我对吗?\n谢谢。
使用双重检查锁定(double-checked locking)模式时,若没有使用volatile关键字,可能会产生以下问题:
1. 线程1进入getBar()方法,发现_barInstance为null。
2. 线程1尝试创建一个Bar对象并更新_barInstance的引用。由于编译器的某些优化,这些操作可能会乱序执行。
3. 与此同时,线程2进入getBar()方法,看到_barInstance非null,但可能会看到_barInstance对象中的成员字段具有默认值。实际上,线程2看到的是一个部分构造的对象,但引用不为null。
为了解决这个问题,可以使用volatile修饰符来禁止对变量_barInstance的写入或读取与之前的读取或写入有关。这样,它可以确保线程2不会看到一个部分构造的对象。
更多详情请参考:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html