来自维基百科的经典破坏双重检查锁定的略微修改版本:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
// Create new Helper instance and store reference on
// stack so other threads can't see it.
Helper myHelper = new Helper();
// Atomically publish this instance.
atomicSet(helper, myHelper);
}
}
}
return helper;
}
}
只是简单地发布新创建的Helper实例原子使得这个双重检查的锁定习惯用法是安全的,假设底层的原子操作库正常工作吗?我意识到在Java中,可以使用volatile
,但即使示例是伪Java,这应该是与语言无关的问题。
另见:
答案 0 :(得分:14)
完全取决于您的平台/语言的完全内存模型。
我的经验法则:就是不要这样做。无锁(或减少锁定,在这种情况下)编程是 hard ,除非你是一个穿线忍者,否则不应该尝试。当你得到真正需要它的分析证明时,你甚至应该考虑它,在这种情况下,你会获得关于特定平台的线程的绝对最佳和最新书并看看它是否可以帮到你。
答案 1 :(得分:7)
我不认为你可以用语言无关的方式回答这个问题而不必完全脱离代码。这完全取决于synchronized
和atomicSet
在伪代码中的工作方式。
答案 2 :(得分:2)
答案取决于语言 - 它归结为atomicSet()
提供的保证。
如果myHelper的构造可以在atomicSet()
之后展开,那么变量如何分配给共享状态无关紧要。
即
// Create new Helper instance and store reference on
// stack so other threads can't see it.
Helper myHelper = new Helper(); // ALLOCATE MEMORY HERE BUT DON'T INITIALISE
// Atomically publish this instance.
atomicSet(helper, myHelper); // ATOMICALLY POINT UNINITIALISED MEMORY from helper
// other thread gets run at this time and tries to use helper object
// AT THE PROGRAMS LEISURE INITIALISE Helper object.
如果语言允许这样做,那么双重检查将不起作用。
答案 3 :(得分:0)
使用volatile不会阻止多个实例化 - 但是使用synchronize会阻止创建多个实例。但是使用你的代码可能会在设置之前返回帮助程序(线程'A'实例化它,但在设置线程'B'之前,helper非空,因此立即将其返回。那个问题,删除第一个if(helper == null)。
答案 4 :(得分:0)
很可能它被打破了,因为部分构造的对象的问题没有得到解决。
答案 5 :(得分:0)
致所有担心部分构建对象的人:
据我所知,部分构造对象的问题只是 构造函数中的问题。换句话说,在构造函数中,如果对象引用自身(包括它的子类)或它的成员,那么部分构造可能存在问题。否则,当构造函数返回时,该类是完全构造的。
我认为你将部分构造与编译器如何优化写入的不同问题混淆起来。编译器可以选择A)为新的Helper对象分配内存,B)将地址写入myHelper(本地堆栈变量),然后C)调用任何构造函数初始化。在B点之后和C点之前的任何时候,访问myHelper将是一个问题。
这是写入的编译器优化,而不是引用文章所关注的部分构造。在原始的单一检查锁定解决方案中,优化的写入可以允许多个线程在点B和C之间查看成员变量。此实现通过使用本地堆栈变量来避免写入优化问题。
所引用论文的主要范围是用双重检查锁解决方案来描述各种问题。但是,除非atomicSet方法也在与Foo类同步,否则此解决方案不是双重检查锁解决方案。它正在使用多个锁。
我想说这一切都归结为原子赋值函数的实现。该函数需要是真正的原子函数,它需要保证处理器本地内存缓存是同步的,并且它需要以比总是同步getHelper方法更低的成本完成所有这些。
根据引用的论文,在Java中,它不太可能满足所有这些要求。此外,本文应该非常清楚的是Java的内存模型经常变化。它可以更好地理解缓存,垃圾收集等的发展,并适应VM运行的底层实际处理器体系结构的变化。
根据经验,如果您以依赖于底层实现的方式优化Java代码(而不是API),则可能会在下一版JVM中出现代码损坏的风险。 (虽然,有时候你别无选择。)
<强> dsimcha:强>
如果您的atomicSet方法是真实的,那么我会尝试将您的问题发送给Doug Lea(以及您的atomicSet实现)。我有一种感觉他是那种会回答的人。我猜测,对于Java,他会告诉你总是同步并在其他地方进行优化会更便宜。