2

我已经阅读了不同的文章,例如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 的字段资源将被分配对新创建对象的引用

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

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

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

4

4 回答 4

2

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

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

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

于 2012-08-23T07:17:52.010 回答
1

“双重检查锁定”是不会消亡的模因之一。恕我直言,使用枚举要聪明得多(正如 Josh Bloch 在 Effective Java 2nd edition 中所建议的)

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

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

简而言之,a)不要使用它 b)使用 Java 5.0+ 的版本 c)不要使用非常旧的不受支持的 Java 版本,并且对非常非常旧的文章(2001 年)持保留态度。

于 2012-08-23T07:30:21.247 回答
1

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

这就是它崩溃的地方。因为线程 Bresource在没有同步的情况下访问,所以它的操作没有读障碍。resource因此,它可能会看到单元格或(稍后)对应于实例的某些字段的单元格的过期缓存副本Resource

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

于 2012-08-23T07:30:29.953 回答
1

我不会说比其他人已经做过的更多,但是因为这是一个经常使用的模式,为什么不为它制作一个实用方法呢?喜欢:供应商记忆

于 2012-08-23T07:43:35.253 回答