为什么Java 5+中的volatile不能确保另一个线程的可见性?

奥列格:

根据:

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;
    }
}

b 当为1,所有的线程a是1。

但是有时我会打印“错误”这怎么可能?

约翰·温特:

更新:

对于有兴趣的人,此错误已得到解决,并已在Java 7u6 build b14中修复。您可以在此处查看错误报告/修复程序

原始答案

在考虑内存可见性/顺序时,您需要考虑其事前发生的关系。的重要前提b != 0a == 1如果是,a != 1则b可以为0或1。

一旦看到a == 1线程,便保证该线程看到b == 1

在OP示例中,在Java 5之后,一旦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,或者更确切地说,它的staticget b似乎有很大的影响(以证明删除此int tt = b;,它应该一直有效。

它出现的负载btt将本地存储的字段,它然后将在如果coniditonal被使用(参考到该值不tt)。因此,如果b == 0为true,则可能意味着to的本地存储为tt0(这是将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时int tt = b,包含时似乎有100%的错误率,而排除它则有0%的机会。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么我不能用HTML5中的另一个空白画布清除画布?

为什么不能降低方法在Java子类中的可见性?

为什么扩展功能在另一个模块中不可见?

为什么当涉及的线程之一是 main() 线程时,线程间可见性不需要 volatile 关键字?

为什么在我的示例中Unsafe.fullFence()无法确保可见性?

从另一个类中的另一个线程更改视图可见性

使用volatile来确保Java中共享(而非并发)数据的可见性

Java线程中的内存顺序和可见性

为什么当我单击一个recyclerView项时,ImageView的可见性是重复的?

Java多线程可见性?

两个Mac App:为什么一个在我的Mac和VMWare 5 VM中运行,而另一个仅在Mac上运行?

为什么Java允许增加子类中受保护方法的可见性?

为什么Java中没有子类可见性修饰符?

PyQt5:一个回调起作用,另一个不起作用-为什么?

Java-如何确保可调用线程返回的数组的可见性

更改一个小部件的可见性时,如何防止pyqt5应用程序中的窗口和小部件更改大小

为什么本地创建的结构不能发送到另一个线程?

为什么在Java 5中不推荐使用java.io.Serializable?

为什么不能在另一个函数中定义一个函数?

为什么FileInputReader.read()一次只能读取5个字节(Java)?

为什么不能从Java的另一个包中的继承类调用受保护的方法?

为什么有时(在一个线程中)效果对其他线程不可见?

为什么容器不能请求另一个?

为什么在CSS中隐藏可见性总是将其设置为可见?

Java中的可变可见性

为什么表单不提交与ASP.NET MVC 5中的模型。(另一个模型绑定错误)

如何设置WPF中另一个类的元素的可见性?

Angular 5:一个指令改变另一个指令的例子是什么?

匿名内部类中Java局部变量的可见性-为什么需要'final'关键字?