有一个article关于 ibm 中 volatile 的使用,其解释让我很困惑,下面是本文中的一个示例及其解释:
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);
}
}
}
Without the theFlooble reference being volatile, the code in doWork() would be at risk for seeing a partially constructed Flooble as it dereferences the theFlooble reference.
如何理解这一点?为什么没有 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()
,发生了 3 次写入:
-
tmpFlooble.x = 5
-
tmpFlooble.y = 1
-
theFlooble = tmpFlooble
如果这些写入按此顺序发生,则一切正常。但没有 volatile
编译器可以自由地重新排序这些写入并按照自己的意愿执行它们。例如。首先是第 3 点,然后是第 1 点和第 2 点。
这种情况实际上一直在发生。编译器确实会对写入进行重新排序。这样做是为了提高性能。
该错误很容易通过以下方式发生:
主题A
执行initInBackground()
类中的方法 BackgroundFloobleLoader
。编译器会在执行 Flooble()
的主体之前对写入进行重新排序。 (其中设置 x
和 y
),线程 A
首先执行theFlooble = new Flooble()
。现在,theFlooble
指向一个flooble实例,其x
和y
是0
。线程之前A
继续,其他一些线程 B
执行方法doWork()
类SomeOtherClass
。该方法调用方法doSomething(floobleLoader.theFlooble)
当前值为theFlooble
。在这个方法中theFlooble.x
除以 theFlooble.y
导致被零除。主题B
由于未捕获的异常而完成。主题A
继续并设置theFlooble.x = 5
和theFlooble.y = 1
.
这种情况当然不会在每次运行时都会发生,但根据 Java 的规则,可能会发生。
关于java - 为什么代码会面临看到部分构造的对象的风险?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34757928/