在ibm中有一个关于volatile的article,解释使我感到困惑,下面是本文的一个示例及其解释:
public class BackgroundFloobleLoader {
public volatile Flooble theFlooble;
public void initInBackground() {
// do lots of stuff
theFlooble = new Flooble(); // this is the only write to theFlooble
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
}
}
如果没有使theFlooble引用不稳定,则doWork()中的代码将因为取消引用theFlooble引用而面临部分构造的Flooble的风险。
怎么理解?为什么不使用volatile,我们可以使用部分构造的
Flooble
对象?谢谢! 最佳答案
没有volatile
,您可能会看到部分构造的对象。例如。考虑这个Flooble
对象。
public class Flooble {
public int x;
public int y;
public Flooble() {
x = 5;
y = 1;
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
public void doSomething(Flooble flooble) {
System.out.println(flooble.x / flooble.y);
}
}
}
如果没有
volatile
,则不能确保doSomething方法看到5
和1
的值x
和y
。例如,它可以看到x == 5
但y == 0
导致被零除。当您执行此操作
theFlooble = new Flooble()
时,将发生三个写入:tmpFlooble.x = 5
tmpFlooble.y = 1
theFlooble = tmpFlooble
如果这些写操作按此顺序进行,则一切正常。但是如果没有
volatile
,编译器可以自由地对这些写入进行重新排序,并根据需要执行它们。例如。首先是点3,然后是点1和2。这实际上一直在发生。编译器确实对写进行了重新排序。这样做是为了提高性能。
该错误很容易以下列方式发生:
线程
A
从类initInBackground()
执行BackgroundFloobleLoader
方法。编译器会对写入进行重新排序,因此在执行Flooble()
的正文(已设置x
和y
的正文)之前,线程A
首先执行theFlooble = new Flooble()
。现在,theFlooble
指向一个floble实例,其x
和y
是0
。在线程A
继续之前,其他一些线程B
执行类doWork()
的方法SomeOtherClass
。此方法使用当前值doSomething(floobleLoader.theFlooble)
调用方法theFlooble
。在此方法中,theFlooble.x
除以theFlooble.y
导致被零除。线程B
由于未捕获的异常而结束。线程A
继续并设置theFlooble.x = 5
和theFlooble.y = 1
。当然,这种情况不会在每次运行时都发生,但是根据Java的规则,可能会发生。