更新:
对于任何感兴趣的人,此错误已在 Java 7u6 build b14 中得到解决和修复。您可以在此处查看错误报告/修复
原始答案
在考虑内存可见性/顺序时,您需要考虑其发生之前的关系。的重要前提条件b != 0
是 for 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
,或者更确切地说是 staticgetb
似乎有很大的影响(为了证明这删除了int tt = b;
并且它应该总是有效的。
看起来b
into的加载tt
将在本地存储该字段,然后在 if coniditonal 中使用该字段(对该值的引用 not tt
)。因此,如果b == 0
为真,则可能意味着本地存储为tt
0(此时将 1 分配给 local 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
和
这没有打印“错误”
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
在此示例中,第一个条目来自打印“错误”的运行,而第二个条目来自未打印的运行。
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)
虽然打印“错误”的运行加载了缓存版本%edx
0x021dd75e: cmp $0x0,%edx
0x021dd761: jne 0x021dd788 ;*ifne
; - Test$1::run@13 (line 17)
对于那些对汇编程序有更多经验的人,请权衡一下:)
编辑 4
应该是我的最后一次编辑,因为并发开发人员得到了帮助,我在有和没有
int tt = b;
分配的情况下做了更多的测试。我发现当我将最大值从 100 增加到 1000 时,包括在内时似乎有 100% 的错误率,int tt = b
而在排除时似乎有 0% 的机会。