为什么不挥发在Java 5+确保从另一个线程的知名度?(Why doesn't volati

2019-06-24 12:40发布

根据:

http://www.ibm.com/developerworks/library/j-jtp03304/

根据新的存储器模型中,当线程A向其写入的易失性可变V,和线程B从V读取,即是可见的那个五获得书面现在保证了时间的任何变量的值是到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。

不过我有时会收到印有“错误”。 这怎么可能?

Answer 1:

更新:

对于任何有兴趣的这个bug已经被解决,并固定为Java 7u6建立B14。 你可以在这里看到的错误报告/修复

  • 报告
  • 变更集
  • Buglist

原来的答案

当在内存能见度/订单的角度思考,你就需要考虑它的之前发生关系。 为重要的先决条件b != 0a == 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楼的窗口7。

编辑3:

分配给tt ,或者说的staticget b似乎有显著的影响(证明这个删除int tt = b;它应该总是工作。

它出现的负载btt将本地存储的字段,它然后将在如果coniditonal(参考该值不被使用tt )。 所以,如果b == 0是真实的,它可能意味着本地商店tt为0(此时它的种族分配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

这并没有打印“错误”

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

在这个例子中,第一项是从印制的“错误”,而第二次是从其中一个didnt运行。

看来,工作运行加载并分配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%的机会。



Answer 2:

基于从下面JCiP提取物,我本来以为你的榜样不应该打印“错误”:

volatile变量的可视性效果超出volatile变量本身的价值。 当一个线程A向其写入的易失性可变,并随后线程B读取相同的变量,即,以写入到易失性可变读取挥发性可变后变得到B可见是可见的,以一种现有的所有变量的值。



Answer 3:

你可能想看看并发兴趣邮件列表上讨论话题在这个问题上: http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html

这似乎是这个问题更容易与客户端JVM(-client)再现。



Answer 4:

在我看来,这个问题acurred由于缺乏同步

注意:如果之前= 1 B = 1个heppens,并且是挥发性的,同时b不是,则b = 1实际上只= 1(根据quate的逻辑)结束后更新所有线程。

什么heppend在你的代码是B = 1第一次更新的主要过程只,然后只有当挥发性分配完成后,所有的线程B的更新。 我想,也许挥发分配不工作的原子操作(需要远点,并以某种方式更新refernces的其余像挥发物),所以这将是我的猜测,为什么一个线程读取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;
        }  
    }
}


文章来源: Why doesn't volatile in java 5+ ensure visibility from another thread?