为什么 java 5+ 中的 volatile 不能确保来自另一个线程的可见性? [英] Why doesn't volatile in java 5+ ensure visibility from another thread?
问题描述
根据:
http://www.ibm.com/developerworks/library/j-jtp03304/
在新的内存模型下,当线程 A 写入易失性变量 V,而线程 B 从 V 读取时,在写入 V 时 A 可见的任何变量值现在保证对 B 可见
Under the new memory model, when thread A writes to a volatile variable V, and thread B reads from V, any variable values that were visible to A at the time that V was written are guaranteed now to be visible to B
互联网上的许多地方都声明以下代码不应打印错误":
And many places on the internet state that the following code should never print "error":
public class Test {
volatile static private int a;
static private int b;
public static void main(String [] args) throws Exception {
for (int i = 0; i < 100; i++) {
new Thread() {
@Override
public void run() {
int tt = b; // makes the jvm cache the value of b
while (a==0) {
}
if (b == 0) {
System.out.println("error");
}
}
}.start();
}
b = 1;
a = 1;
}
}
b
应该当a
为1时所有线程都为1.
b
should be 1 for all the threads when a
is 1.
但是我有时会打印错误".这怎么可能?
推荐答案
更新:
对于任何感兴趣的人来说,Java 7u6 build b14 已经解决并修复了这个错误.您可以在此处查看错误报告/修复
For anyone interested this bug has been addressed and fixed for Java 7u6 build b14. You can see the bug report/fixes here
原答案
在考虑内存可见性/顺序时,您需要考虑其发生在之前的关系.b != 0
的重要前提条件是 a == 1
.如果 a != 1
那么 b 可以是 0 或 1.
When thinking in terms of memory visibility/order you would need to think about its happens-before relationship. The important pre condition for b != 0
is for a == 1
. If a != 1
then b can be either 0 or 1.
一旦一个线程看到a == 1
,那么该线程肯定会看到b == 1
.
Once a thread sees a == 1
then that thread is guaranteed to see b == 1
.
Java 5 后,在 OP 示例中,一旦 while(a == 0)
突破 b 保证为 1
Post Java 5, in the OP example, once the while(a == 0)
breaks out b is guaranteed to be 1
我多次运行模拟,但没有看到您的输出.
I ran the simulation many number of times and didn't see your output.
什么操作系统,Java 版本 &你在测试 CPU 吗?
What OS, Java version & CPU are you testing under?
我使用的是 Windows 7,Java 1.6_24(尝试使用 _31)
I am on Windows 7, Java 1.6_24 (trying with _31)
编辑 2:
感谢 OP 和 Walter Laan - 对我来说,只有当我从 64 位 Java 切换到 32 位 Java 时才会发生这种情况,在(但可能不排除到)64 位 Windows 7 上.
Kudos to the OP and Walter Laan - For me it only happened when I switched from 64 bit Java to 32 bit Java, on (but may not be excluded to) a 64 bit windows 7.
编辑 3:
对 tt
的赋值,或者说 b
的 staticget 似乎有重大影响(为了证明这一点,删除 int tt = b;代码>,它应该始终有效.
The assignment to tt
, or rather the staticget of b
seems to have a significant impact (to prove this remove the int tt = b;
and it should always work.
看起来 b
到 tt
的加载将在本地存储该字段,然后将在 if coniditonal 中使用该字段(对该值的引用不是 tt
).因此,如果 b == 0
为真,则可能意味着 tt
的本地存储为 0(此时将 1 分配给本地 tt代码>).这似乎只适用于 32 位 Java 1.6 &7 与客户端设置.
It appears the load of b
into tt
will store the field locally which will then be used in the if coniditonal (the reference to that value not tt
). So if b == 0
is true it probably means that the local store to tt
was 0 (at this point its a race to assign 1 to local tt
). This seems only to be true for 32 Bit Java 1.6 & 7 with client set.
我比较了两个输出程序集,直接的区别就在这里.(请记住,这些是片段).
I compared the two output assembly and the immediate difference was here. (Keep in mind these are snippets).
这个打印的错误"
0x021dd753: test %eax,0x180100 ; {poll}
0x021dd759: cmp $0x0,%ecx
0x021dd75c: je 0x021dd748 ;*ifeq
; - Test$1::run@7 (line 13)
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test$1::run@13 (line 17)
0x021dd767: nop
0x021dd768: jmp 0x021dd7b8 ; {no_reloc}
0x021dd76d: xchg %ax,%ax
0x021dd770: jmp 0x021dd7d2 ; implicit exception: dispatches to 0x021dd7c2
0x021dd775: nop ;*getstatic out
; - Test$1::run@16 (line 18)
0x021dd776: cmp (%ecx),%eax ; implicit exception: dispatches to 0x021dd7dc
0x021dd778: mov $0x39239500,%edx ;*invokevirtual println
还有
这没有打印错误"
0x0226d763: test %eax,0x180100 ; {poll}
0x0226d769: cmp $0x0,%edx
0x0226d76c: je 0x0226d758 ;*ifeq
; - Test$1::run@7 (line 13)
0x0226d76e: mov $0x341b77f8,%edx ; {oop('Test')}
0x0226d773: mov 0x154(%edx),%edx ;*getstatic b
; - Test::access$0@0 (line 3)
; - Test$1::run@10 (line 17)
0x0226d779: cmp $0x0,%edx
0x0226d77c: jne 0x0226d7a8 ;*ifne
; - Test$1::run@13 (line 17)
0x0226d782: nopw 0x0(%eax,%eax,1)
0x0226d788: jmp 0x0226d7ed ; {no_reloc}
0x0226d78d: xchg %ax,%ax
0x0226d790: jmp 0x0226d807 ; implicit exception: dispatches to 0x0226d7f7
0x0226d795: nop ;*getstatic out
; - Test$1::run@16 (line 18)
0x0226d796: cmp (%ecx),%eax ; implicit exception: dispatches to 0x0226d811
0x0226d798: mov $0x39239500,%edx ;*invokevirtual println
在此示例中,第一个条目来自打印错误"的运行,而第二个条目来自没有打印的运行.
In this example the first entry is from a run that printed "error" while the second was from one which didnt.
在测试它等于 0 之前,工作运行似乎已正确加载并分配了 b
.
It seems that the working run loaded and assigned b
correctly before testing it equal to 0.
0x0226d76e: mov $0x341b77f8,%edx ; {oop('Test')}
0x0226d773: mov 0x154(%edx),%edx ;*getstatic b
; - Test::access$0@0 (line 3)
; - Test$1::run@10 (line 17)
0x0226d779: cmp $0x0,%edx
0x0226d77c: jne 0x0226d7a8 ;*ifne
; - Test$1::run@13 (line 17)
虽然打印错误"的运行加载了 %edx
While the run that printed "error" loaded the cached version of %edx
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test$1::run@13 (line 17)
对于那些有更多汇编经验的人,请权衡:)
For those who have more experience with assembler please weigh in :)
编辑 4
应该是我最后一次编辑,因为并发开发人员已经掌握了它,我在有和没有的情况下进行了测试int tt = b;
再赋值一些.我发现当我将最大值从 100 增加到 1000 时,当包含 int tt = b
时似乎有 100% 的错误率,而排除它时的错误率为 0%.
Should be my last edit, as the concurrency dev's get a hand on it, I did test with and without the
int tt = b;
assignment some more. I found that when I increase the max from 100 to 1000 there seems to be a 100% error rate when int tt = b
is included and a 0% chance when it is excluded.
这篇关于为什么 java 5+ 中的 volatile 不能确保来自另一个线程的可见性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!