为什么java 5+中的volatile不能确保其他线程的可见性?
根据:
http://www.ibm.com/developerworks/library/j-jtp03304/
在新的内存模型下,当线程A写入一个易失性variablesV,并且线程B从V读取时,在写入V时A可见的任何variables值现在保证对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; } }
当a
为1时,对于所有线程, b
应该是1。
不过, 我有时会打印“错误” 。 这怎么可能?
更新:
对于任何感兴趣的人来说,这个bug已经被解决并且被固定用于Java 7u6 build b14。 你可以在这里看到bug报告/修复
- 报告
- 变更集
- Buglist
原始答复
当从内存可见性/秩序的angular度来考虑时,你需要考虑它发生的事情之前的关系。 b != 0
的重要前提条件是a == 1
。 如果a != 1
那么b可以是0或1。
一旦线程看到a == 1
那么该线程保证看到b == 1
。
发布Java 5,在OP示例中,一旦while(a == 0)
发生,b保证为1
编辑:
我跑了很多次的模拟,没有看到你的输出。
你在testing什么操作系统,Java版本和CPU?
我在Windows 7上,Java 1.6_24(用_31尝试)
编辑2:
对OP和Walter Laan的荣誉 – 对我来说,只有当我从64位Java切换到32位Java时,才会发生(但可能不排除)64位Windows 7。
编辑3:
对tt
的赋值,或者对b
的静态赋值似乎具有显着的影响(certificate这个删除int tt = b;
它应该总是有效的。
看来b
到tt
的负载将把本地的字段存储起来,然后用在if coniditonal(对那个值的引用不是tt
)中。 所以如果b == 0
是真的,那么可能意味着本地存储到tt
是0(在这一点上,它是一个为本地tt
分配1的竞赛)。 这似乎只适用于客户端设置的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
在这个例子中,第一个条目是从打印“错误”的运行,而第二个条目是从没有打印的错误。
在testing之前,似乎正确地加载并分配了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
的caching版本
0x021dd75e: cmp $0x0,%edx 0x021dd761: jne 0x021dd788 ;*ifne ; - Test$1::run@13 (line 17)
对于那些对汇编有更多经验的人,请称重:)
编辑4
应该是我最后一次编辑,因为并发开发人员掌握了它,我做了testing,没有int tt = b;
再分配一些。 我发现,当我将最大值从100增加到1000时,当包含int tt = b
时,似乎有100%的错误率,排除时有0%的机会。
根据以下JCiP的摘录,我会认为你的例子不应该打印“错误”:
易失性variables的可见性效应超出了易失性variables本身的价值。 当线程A写入一个易失性variables,然后线程B读取相同的variables时,写入易失性variables之前A对A可见的所有variables的值在读取volatilevariables后对B可见。
您可能想要查看关于此问题的并发兴趣邮件列表上的讨论主题: http : //cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html
似乎问题更容易与客户端JVM(客户端)复制。
在我看来,由于缺乏同步而引起的问题:
注意:如果b = 1在a = 1之前发生了heap,并且a是volatile而b不是,那么b = 1只有在a = 1结束之后(根据quate逻辑)才会实际更新所有线程。
在你的代码中,b = 1只是在主进程中第一次被更新,那么只有在volatile赋值完成之后,所有线程b才会被更新。 我认为可能是volatile的赋值不是作为primefaces操作工作的(需要指向很远,并且以某种方式更新引用的其余部分以便像volatile一样),所以这就是我猜测为什么一个线程读取b = 0而不是b = 1的原因。
考虑到代码的这种变化,显示我的说法:
public class Test { volatile static private int a; static private int b; private static Object lock = new Object(); 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 (true) { synchronized (lock ) { if (a!=0) break; } } if (b == 0) { System.out.println("error"); } } }.start(); } b = 1; synchronized (lock ) { a = 1; } } }