双重检查锁定的Java同步效果?

时间:2012-08-23 07:12:32

标签: java multithreading synchronized java-memory-model double-checked-locking

我已阅读Double-checked locking: Clever, but broken之类的不同文章,并且我理解为什么以下代码在多线程使用中被破坏的原因。

class SomeClass {
  private Resource resource = null;
  public Resource getResource() {
    if (resource == null) {
      synchronized {
        if (resource == null) 
          resource = new Resource();
      }
    }
    return resource;
  }
}

但是,根据其解释,当一个线程退出同步块时,它会执行写屏障 - 它必须在释放锁之前将该块中修改的任何变量清除到主内存。因此,当线程A进入同步块时,然后按顺序执行以下过程:

  1. 将分配新Resource对象的内存;
  2. 将调用Resource的构造函数,
  3. 初始化新对象的成员字段;
  4. 将为SomeClass的字段资源分配对新创建的对象的引用
  5. 最后,在线程A退出synchronized块之前,它会将其本地资源对象写回主内存,然后线程B将在主内存中运行同步块后从主内存中读取这个新创建的资源。

    为什么线程B可能会以与线程A执行的顺序不同的顺序看到这些内存操作?我认为线程B不会知道资源对象是否已创建,直到线程A将其本地内存刷新从同步块退出时的主内存,因为线程B只能从可共享的主内存中读取资源对象?

    请纠正我的理解....谢谢。

4 个答案:

答案 0 :(得分:2)

您引用的文章是指Java 5.0之前的Java内存模型。

在Java 5.0+中,您的resource必须声明为volatile才能生效。即使更改被刷新到主存储器,也不能保证(除了volatile之外)线程B将从主存储器读取新值而不是其自己的本地缓存(其中值为空)。

在以前的版本中,volatile没有对重新排序施加严格限制,因此无法保证双重检查锁定无法正常工作。

答案 1 :(得分:1)

“双重检查锁定”其中一个不会死的模因。使用枚举的恕我直言更聪明(正如Josh Bloch在Effective Java第2版中所建议的那样)

enum SomeClass {
    INSTANCE; // thread safe and lazy loaded
}

您指的错误已在2004年的Java 5.0中修复。

简而言之,a)不要使用它b)使用Java 5.0+版本c)不要使用真正旧的不受支持的Java版本,并采取真正的旧文章(2001)。

答案 2 :(得分:1)

  

最后,在线程A退出synchronized块之前,它会将其本地资源对象写回主内存,然后线程B将在主内存中运行同步块后从主内存中读取这个新创建的资源。

这是它崩溃的地方。由于线程B在没有同步的情况下访问resource,因此其操作没有读取障碍。因此,它可能会看到resource单元格的内存缓存副本或({稍后一点)对应于Resource实例的某个字段的单元格。

Costi Ciudatu的修正对于Java版本> = 5.0是正确的。但对于早于此版本的版本,volatile的语义并不能保证所有更改都会从A到主内存刷新到B。

答案 3 :(得分:1)

我不会说其他人已经做过的更多,但因为这是一种常用的模式,为什么不为它做一个实用的方法呢?像:Suppliers Memoize