此问题与旧Java版本的行为和双重检查锁定算法的旧实现有关
较新的实现use volatile
并且依赖于略微更改的volatile
语义,因此它们不已损坏。
据说,除了long或double字段外,字段赋值总是原子的。
但是,当我读到为什么双重检查锁定被打破的解释时,它说的是问题在于赋值操作:
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
// other functions and members...
}
- 线程A注意到该值未初始化,因此它获得了 锁定并开始初始化 值。
- 由于某些编程语言的语义,代码 允许编译器生成 将共享变量更新为point 到部分构造的对象 在A完成表演之前 初始化。
- 线程B注意到共享变量已初始化(或左右) 它出现),并返回其值。 因为线程B认为价值是 已初始化,但事实并非如此 获得锁。如果B使用该对象 在完成所有初始化之前 由A看到A(因为A 尚未完成初始化或 因为一些初始化的值 在对象尚未渗透 到内存B使用(缓存 连贯)),程序很可能 崩溃。
醇>
(来自http://en.wikipedia.org/wiki/Double-checked_locking)。
什么时候可能?是否有可能在64位JVM分配操作中不是原子的? 如果不是那么“双重检查锁定”是否真的被打破了?
答案 0 :(得分:15)
问题不是原子性,而是排序。只要未违反happens-before,JVM就可以重新排序指令以提高性能。因此,运算符理论上可以在来自类helper
的构造函数的所有指令执行之前调度更新Helper
的指令。
答案 1 :(得分:7)
引用的赋值是原子的,但构造不是!因此,如解释中所述,假设线程B想要在线程A完全构造它之前使用单例,它不能创建新实例,因为引用不是null,因此它只返回部分构造的对象。
如果您不确保发布 共享引用发生在之前 另一个线程加载共享 参考,然后写的 引用新对象即可 对其写入重新排序 领域。在那种情况下,另一个线程 可以看到最新的价值 对象引用但过时了 部分或全部对象的值 国家 - 部分建构的 宾语。 - Brian Goetz:Java Concurrency in Practice
由于初始检查null未同步,因此没有发布,并且可以进行此重新排序。
答案 2 :(得分:2)
在构造函数中构造Helper
的实例可能需要多个赋值,并且语义允许它们根据赋值helper = new Helper()
进行重新排序。
因此,可以为字段helper
分配对未进行所有分配的对象的引用,以使其未完全初始化。
答案 3 :(得分:2)
在java中进行双重检查锁定存在各种问题:
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
答案 4 :(得分:1)
阅读本文:http://www.javaworld.com/jw-02-2001/jw-0209-double.html 即使你不理解所有的细节(像我一样),只要相信这个好的技巧就行不通了。
答案 5 :(得分:0)
对不起,这可能与这个问题有点无关,我只是好奇。 在这种情况下,在分配之前获取锁和/或返回值是不是更好?像:
private Lock mLock = new ReentrantLock();
private Helper mHelper = null;
private Helper getHelper() {
mLock.lock();
try {
if (mHelper == null) {
mHelper = new Helper();
}
return mHelper;
}
finally {
mLock.unlock();
}
}
或者使用双重检查锁定是否有任何优势?
答案 6 :(得分:-1)
/*Then the following should work.
Remember: getHelper() is usually called many times, it is BAD
to call synchronized() every time for such a trivial thing!
*/
class Foo {
private Helper helper = null;
private Boolean isHelperInstantiated;
public Helper getHelper() {
if (!isHelperInstantiated) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
isHelperInstantiated = true;
}
}
}
return helper;
}
// other functions and members...
}