我已阅读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进入同步块时,然后按顺序执行以下过程:
最后,在线程A退出synchronized块之前,它会将其本地资源对象写回主内存,然后线程B将在主内存中运行同步块后从主内存中读取这个新创建的资源。
为什么线程B可能会以与线程A执行的顺序不同的顺序看到这些内存操作?我认为线程B不会知道资源对象是否已创建,直到线程A将其本地内存刷新从同步块退出时的主内存,因为线程B只能从可共享的主内存中读取资源对象?
请纠正我的理解....谢谢。
答案 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