在Singleton类的情况下,为什么不检查双重锁定失败呢?

时间:2014-07-17 18:19:43

标签: java multithreading singleton synchronized double-checked-locking

1.   class Foo {
2.       private Helper helper = null;
3.       public Helper getHelper() {
4.           if (helper == null) {
5.              synchronized(this) {
6.                 if (helper == null) {
7.                    helper = new Helper();
8.                 }
9.              }
10.          }
11.          return helper;
12.      }
13.  }

这种结构被认为是破坏的原因通常描述了编译器完成的赋值的重新排序,以便在写入辅助变量之后调用Helper构造函数。我的问题是这个代码如何是线程安全的,并且可能采取以下步骤?

  • 线程1,进入synchronized块并发现帮助器为空。
  • 线程1,此时放弃显示器
  • 线程2,进入对象监视器并实例化助手
  • 线程1,返回并重新初始化帮助程序实例

我没有看到这个解决方案比单个检查锁定更好。

3 个答案:

答案 0 :(得分:4)

这适用于帮助器的引用,但仍然会被巧妙地破坏。

它已损坏,因为允许VM根据需要重新排序同步块中的程序操作,因此引用帮助程序可以设置为非null < em>在构造helper实例之前(通过线程1)已经完成。

线程2现在可以在synchronized块之外看到not-null,永远不会尝试进入synchronized块(线程1仍然会持有锁并忙于构建Helper)并使用半构造的Helper实例。

这可能会也可能不会发生在特定的VM版本上。但规范明确允许VM执行此操作。这就是为什么这个例子被打破了。可以通过声明辅助volatile(仅使用Java 5 +)来修复它。

答案 1 :(得分:2)

在检查帮助程序为空后,线程1如何放弃监视器?在初始化帮助程序之前,它不会释放锁。

这几年前在JVM上没有工作,但他们改变了内存模型,并修复了这个问题。

当前&#34;最佳&#34;方式不是DCL,而是实施singletons as enums.

答案 2 :(得分:0)

它仍然被破坏,因为即使Helper实例不为null,也可能无法发布Helper内的任何字段的写入。例如:

class Helper {
   int someField;
   public Helper(){
     someField = 10;
   }
}

在这种情况下,根据JLS,可能有类似的内容:

Helper someHelper = getHelper();
if(someHelper.someField == 0){
   // error
}

理论上标记为//error的行可能会被读取&amp; someField的写入与helper中的getHelper()的写入不同步。