JAVA中的双重检查锁定
阅读, 我想知道DCL中的问题和建议的解决方案,或者换句话说,为什么需要JAVA中的双重检查锁定,java,multithreading,thread-safety,double-checked-locking,Java,Multithreading,Thread Safety,Double Checked Locking,阅读, 我想知道DCL中的问题和建议的解决方案,或者换句话说,为什么需要volatile关键字? 简而言之,问题在于:在某些情况下,使用DCL可能会导致线程使用部分构造的对象。建议的解决方案(针对JAVA 5+): 现在,我的问题是为什么不从helper中删除volatile?如果我错了,请纠正我,但如果您只是打断这行:helper=result=newhelper()至: result = new Helper(); helper = result; 在这种情况下,助手在对象完成之前将永远不
volatile
关键字?
简而言之,问题在于:在某些情况下,使用DCL可能会导致线程使用部分构造的对象。建议的解决方案(针对JAVA 5+):
现在,我的问题是为什么不从helper中删除volatile
?如果我错了,请纠正我,但如果您只是打断这行:helper=result=newhelper()代码>至:
result = new Helper();
helper = result;
在这种情况下,助手在对象完成之前将永远不会获得引用。不是吗?volatile
如何做得更好
编辑:假设此代码:
class Foo {
private volatile Helper helper;
public Helper getHelper() {
Helper result = helper;
if (result == null) {
synchronized(this) {
result = helper;
if (result == null) {
result = new Helper();
result.someMethod();
helper = result;
}
}
}
return result;
}
}
如果初始化后的下一行不能保证一个完全初始化的对象,我就不能对它调用方法。我可以吗?如果省略volatile
,编译器可以完全优化结果。
这个额外的局部变量变化不大。它节省了额外的负载,但修复竞争条件的是volatile
关键字
假设我们有以下代码片段:
public volatile Object helper;
public synchronized void initHelper() {
Object result = new Object();
helper = result;
}
它将被编译为以下伪程序集:
public synchronized void initHelper() {
Object result = Object.new();
result.<init>();
// result is now fully initialized
// but helper is still null for all threads
this.helper = result;
// now all threads can access fully initialized helper
}
如果在initHelper
中添加函数调用。编译器不会在初始化之前进行此调用。即使在单线程中,这也会改变程序的含义。但是允许它进行优化,这将中断访问此字段的多线程代码。因为result=new Helper();助手=结果
与helper=result=newhelper()完全相同代码>。您认为会有什么不同?显然,您假设所有操作都将按照与程序顺序一致的总顺序执行。一般来说,多线程程序不是这样。@nosid,我意识到初始化行可能会在初始化完成之前收到地址,但我假设只有在初始化完成后才会执行下一行。否则,在实例化对象之后,我怎么能使用它呢?如果它已完全初始化,我现在就永远无法访问它?请参见编辑。@YekhezkelYovel:单个线程中的操作将始终按程序顺序执行。对于多个线程,如果没有数据争用,则执行将看起来很简单。您的代码在helper
上包含一个数据竞争,如果变量不是volatile
@YekhezkelYovel:在第二个列表中,变量helper
在哪里更新?谢谢@Banthar。它让我更好地理解了volatile
,尽管我仍然有点困惑。请看我的编辑。谢谢@Banthar。如果我使用volatile
关键字但不使用局部变量,它会是什么样子?转换不正确,因为
可能会引发异常。在这种情况下,它不会更改任何内容。编译器将自己添加一个临时变量,以便在赋值给字段之前完成初始化<代码>助手
只被分配了一次-编译器不允许更改它。@nosid您是对的。这个例子是一个粗略的简化。也许我不应该称之为
,因为这不是真正的构造函数。
public synchronized void initHelper() {
Object result = Object.new();
result.<init>();
// result is now fully initialized
// but helper is still null for all threads
this.helper = result;
// now all threads can access fully initialized helper
}
public synchronized void initHelper() {
this.helper = Object.new();
// from now on other threads can access helper
this.helper.<init>();
// but it's fully initialized only after this line
}