更新:
对于任何感兴趣的人,这个错误已经得到解决并修复了Java 7u6版本的b14构建。您可以在这里查看错误报告/修复。
原始答案
在考虑内存可见性/顺序时,您需要考虑其发生之前的关系。对于b != 0
的重要前提是a == 1
。如果a != 1
,那么b可以是0或1。
一旦一个线程看到a == 1
,那么该线程就保证看到b == 1
。
在Java 5之后,在OP的例子中,一旦
while(a == 0)
跳出循环,b的值就保证是1。
编辑:
我运行了很多次模拟,但没有看到你的输出。
你在哪个操作系统、Java版本和CPU下进行测试?
我使用的是Windows 7,Java 1.6_24(尝试使用_31)。
编辑2:
向OP和Walter Laan致敬-对我来说,只有当我从64位Java切换到32位Java时,才会出现这种情况,而且可能不仅限于64位的Windows 7。
编辑3:
对
tt
的赋值,或者更确切地说是
b
的静态获取似乎有很大的影响(要证明这一点,请删除
int tt = b;
,然后它应该总是正常工作)。
看起来将
b
加载到
tt
中将在本地存储该字段,然后在if条件中使用(对该值的引用不是
tt
)。所以如果
b == 0
为真,这可能意味着对
tt
的本地存储为0(此时将1分配给本地
tt
是一场竞赛)。这似乎只对32位Java 1.6和7与客户端设置为真。
我比较了两个输出汇编代码,立即发现了差异。(请记住这些只是片段)。
这打印了"error"。
0x021dd753: test %eax,0x180100
0x021dd759: cmp $0x0,%ecx
0x021dd75c: je 0x021dd748
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788
0x021dd767: nop
0x021dd768: jmp 0x021dd7b8
0x021dd76d: xchg %ax,%ax
0x021dd770: jmp 0x021dd7d2
0x021dd775: nop
0x021dd776: cmp (%ecx),%eax
0x021dd778: mov $0x39239500,%edx
而且
这并没有打印出“错误”
0x0226d763: test %eax,0x180100
0x0226d769: cmp $0x0,%edx
0x0226d76c: je 0x0226d758
0x0226d76e: mov $0x341b77f8,%edx
0x0226d773: mov 0x154(%edx),%edx
0x0226d779: cmp $0x0,%edx
0x0226d77c: jne 0x0226d7a8
0x0226d782: nopw 0x0(%eax,%eax,1)
0x0226d788: jmp 0x0226d7ed
0x0226d78d: xchg %ax,%ax
0x0226d790: jmp 0x0226d807
0x0226d795: nop
0x0226d796: cmp (%ecx),%eax
0x0226d798: mov $0x39239500,%edx
在这个例子中,第一个条目是来自一个打印了"error"的运行,而第二个条目是来自一个没有打印"error"的运行。
看起来,在测试变量
b
等于0之前,工作运行成功加载并赋值了它。
0x0226d76e: mov $0x341b77f8,%edx
0x0226d773: mov 0x154(%edx),%edx
0x0226d779: cmp $0x0,%edx
0x0226d77c: jne 0x0226d7a8
在打印“错误”的运行中,加载了缓存版本的
%edx
。
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788
对于那些在汇编语言方面有更多经验的人,请发表意见 :)
编辑4
这应该是我最后一次编辑了,因为并发开发人员已经开始处理了,我对包含和不包含
int tt = b;
赋值进行了更多测试。我发现当我将最大值从100增加到1000时,当包含
int tt = b;
时,错误率似乎达到了100%,而当不包含时,错误率为0%。
a
进行 volatile 写入/读取后,b
的各种缓存值没有同步到b=1
。 - yshavitb
是否是volatile,都没有关系,因为对它的写入后面跟随着对一个volatile变量的写入,在另一个线程中对它的读取在读取相同的volatile变量之前。 - Marko Topolnik