Ole*_*leg 69 java concurrency multithreading volatile compiler-bug
根据:
http://www.ibm.com/developerworks/library/j-jtp03304/
在新的内存模型下,当线程A写入易失性变量V,并且线程B从V读取时,在写入V时对A可见的任何变量值现在都保证对B可见.
互联网上的许多地方声明以下代码永远不应该打印"错误":
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;
}
}
Run Code Online (Sandbox Code Playgroud)
b 当a为1 时,所有线程应为1.
但是我有时会打印出"错误".这怎么可能?
Joh*_*int 34
更新:
对于任何感兴趣的人来说,这个bug已经针对Java 7u6 build b14进行了解决和修复.您可以在此处查看错误报告/修复
原始答案
在考虑内存可见性/顺序时,您需要考虑它之前发生的关系.重要的前提条件b != 0是a == 1.如果a != 1那么b可以是0或1.
一旦线程看到,a == 1那么该线程可以保证看到b == 1.
在OP示例中,一旦while(a == 0)突破b保证为1 ,就发布Java 5
编辑:
我多次运行模拟并没有看到你的输出.
你在测试什么操作系统,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;它应该始终有效.
看来binto 的加载tt将在本地存储字段,然后将在if coniditonal中使用(不是对该值的引用tt).因此,如果b == 0为真,则可能意味着本地商店为tt0(此时它是一个将1分配给本地的竞赛tt).这似乎只适用于具有客户端设置的32位Java 1.6和7.
我比较了两个输出组件,这里有直接的区别.(请记住这些是片段).
这印刷的"错误"
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
Run Code Online (Sandbox Code Playgroud)
和
这没有打印"错误"
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
Run Code Online (Sandbox Code Playgroud)
在这个例子中,第一个条目来自打印"错误"的运行,而第二个条目来自没有打印的错误.
工作运行似乎b在测试之前正确加载和分配等于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)
Run Code Online (Sandbox Code Playgroud)
打印"错误"的运行加载了缓存版本 %edx
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test$1::run@13 (line 17)
Run Code Online (Sandbox Code Playgroud)
对于那些有汇编程序经验的人,请权衡:)
编辑4
应该是我的最后一次编辑,因为并发开发者可以得到它,我确实测试了有没有int tt = b;更多的
任务.我发现当我将最大值从100增加到1000时int tt = b,包含时的错误率似乎为100%,排除时则为0%.
ass*_*ias 12
根据以下JCiP的摘录,我认为你的例子永远不应该打印"错误":
volatile变量的可见性效果超出了volatile变量本身的值.当线程A写入volatile变量并且随后线程B读取同一个变量时,在写入volatile变量之前A可见的所有变量的值在读取volatile变量后变为B可见.