尽管没有代码明确泄漏它,但未初始化的对象泄漏到另一个线程?
让我们看看这个简单的 Java 程序:
Let's see this simple Java program:
import java.util.*;
class A {
static B b;
static class B {
int x;
B(int x) {
this.x = x;
}
}
public static void main(String[] args) {
new Thread() {
void f(B q) {
int x = q.x;
if (x != 1) {
System.out.println(x);
System.exit(1);
}
}
@Override
public void run() {
while (b == null);
while (true) f(b);
}
}.start();
for (int x = 0;;x++)
b = new B(Math.max(x%2,1));
}
}
主线程
主线程创建 B
的实例,并将 x
设置为 1,然后将该实例写入静态字段 A.b
.它会永远重复这个动作.
The main thread creates an instance of B
with x
set to 1, then writes that instance to the static field A.b
. It repeats this action forever.
轮询线程
生成的线程会轮询,直到发现 A.b.x
不是 1.
The spawned thread polls until it finds that A.b.x
is not 1.
?!?
一半的时间按预期进入无限循环,但一半的时间我得到这个输出:
Half the time it goes in an infinite loop as expected, but half the time I get this output:
$ java A
0
为什么轮询线程能够看到 x
未设置为 1 的 B
?
Why is the polling thread able to see a B
that has x
not set to 1?
x%2
而不仅仅是 x
在这里仅仅是因为问题可以重现.
x%2
instead of just x
is here simply because the issue is reproducible with it.
我在 linux x64 上运行 openjdk 6.
I'm running openjdk 6 on linux x64.
推荐答案
这是我的想法:因为 b 不是 final,编译器可以随意重新排序操作,对吧?所以这从根本上说是一个重新排序问题,结果是 不安全的发布问题将变量标记为 final 将解决问题.
Here is what I think: because b is not final, the compiler is free to reorder the operations as it likes, right? So this, fundamentally is a reordering issue and as a result a unsafe publication issue Marking the variable as final will fix the problem.
或多或少,它与 Java 内存模型文档.
真正的问题是这怎么可能.我也可以在这里推测(因为我不知道编译器将如何重新排序),但可能在写入 x 之前,对 B 的引用被写入主内存(另一个线程可见).在这两个操作之间发生读取,因此零值
The real question is how is this possible. I can also speculate here (since I have no idea how the compiler will reorder), but maybe the reference to B is written to the main memory (where it is visible to the other thread) BEFORE the write to x happens. In between these two operations the read happens, thus the zero value
相关文章