为什么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;它应该总是有效的。

看来btt的负载将把本地的字段存储起来,然后用在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之前AA可见的所有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; } } }