当前位置: 首页 > 知识库问答 >
问题:

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

景唯
2023-03-14

根据:

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

a为1时,所有线程的b应为1。

然而,我有时会打印“错误”。这怎么可能呢?

共有3个答案

姚文轩
2023-03-14

您可能想查看并发兴趣邮件列表中有关此问题的讨论线程:http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html

客户机JVM(-client)似乎更容易重现这个问题。

许出野
2023-03-14

根据下面JCiP的摘录,我认为您的示例永远不应该打印“错误”:

可变变量的可见性影响超出了可变变量本身的值。当线程a写入易失性变量,然后线程B读取该变量时,在写入易失性变量之前对a可见的所有变量的值在读取易失性变量后对B可见。

应向晨
2023-03-14

更新:

对于任何感兴趣的人来说,Java 7u6 build b14已经解决并修复了这个错误。你可以在这里看到错误报告/修复

  • 报告
  • 变更集
  • 错误列表

原始答案

当考虑记忆的可见性/顺序时,你需要考虑它发生在关系之前。b!=0代表a==1。如果a!=1那么b可以是0或1。

一旦一个线程看到a==1,那么该线程就保证看到b==1

Java 5之后,在OP示例中,一旦while(a==0)中断,b保证为1

编辑:

我多次运行模拟,没有看到您的输出。

什么操作系统,Java版本

我在Windows 7Java16_24(尝试_31)

编辑2:

感谢OP和Walter Laan——对我来说,这只发生在我从64位Java切换到32位Java,在(但可能不排除在)64位windows 7上时。

编辑3:

分配给tt,或者更确切地说,分配给b的staticget似乎具有重大影响(为了证明这一点,请删除int tt=b;,它应该始终有效。

似乎加载到tt中的b将本地存储字段,然后在if coniditonal中使用该字段(对该值的引用不是tt)。所以如果b==0是true,这可能意味着tt的本地存储是0(在这一点上,将1分配给本地tt是一场竞赛)。这似乎只适用于32位Java1.6

我比较了两个输出组件,直接的区别就在这里。(请记住,这些是片段)。

这个打印的“错误”

 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)
  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)

对于那些有更多组装经验的人,请权衡:)

编辑4

这应该是我的最后一次编辑了,因为并发开发人员已经开始着手了,我测试了int tt=b和int tt=b 作业更多。我发现,当我将最大值从100增加到1000时,当包含int tt=b时,似乎有100%的错误率,而当排除时,错误率为0%。

 类似资料:
  • 问题内容: 根据: http://www.ibm.com/developerworks/library/j-jtp03304/ 在新的内存模型下,当线程A写入易失性变量V,并且线程B从V读取时,现在保证了在写入V时A可见的任何变量值对B可见。 互联网上的许多地方都指出,以下代码永远不应显示“错误”: 应 当为1,所有的线程是1。 但是 有时我会打印“错误” 。这怎么可能? 问题答案: 更新: 对于

  • volatile只能作用于变量,保证了操作可见性和有序性,不保证原子性。 在Java的内存模型中分为主内存和工作内存,Java内存模型规定所有的变量存储在主内存中,每条线程都有自己的工作内存。 主内存和工作内存之间的交互分为8个原子操作: lock unlock read load assign use store write volatile修饰的变量,只有对volatile进行assign操作

  • 主要内容:可见性,原子性,举个例子首先要了解的是,volatile可以保证可见性和顺序性,这些都很好理解,那么它为什么不能保证原子性呢? 可见性 可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量

  • 主要内容:可见性,原子性,举个例子首先要了解的是,volatile可以保证可见性和顺序性,这些都很好理解,那么它为什么不能保证原子性呢? 可见性 可见性与Java的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量

  • 问题内容: 假设存在以下基类: 同样,在同一个程序包中,有一个继承的类: 然后,另一个包中有第三类: 最后,还有一个实现类,同样在另一个包中: 尽管继承,而且是,有以下错误: getHeight()在bg.svetlin.ui.controls.Control中已保护访问 我检查了我的进口货物是否正确。我正在使用NetBeans。 知道有什么问题吗?我认为即使孩子处于不同的包装中,孩子们也可以看到

  • 我正在尝试实现LZ77的快速版本,我有一个关于并发编程的问题要问你。 现在我有一个和一个,两者的长度相同。该程序执行以下操作: > 主线程写入所有缓冲区,然后通知线程并等待它们完成。 单个工作线程处理缓冲区的一部分,并将结果保存在结果保持器的同一部分中。工人部分是排他性的。之后,主线程会收到通知,工作人员会暂停。 当所有的工作都暂停时,主线程读取结果持有器中的数据并更新缓冲区,然后(如果需要)过程