gpt4 book ai didi

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

转载 作者:IT老高 更新时间:2023-10-28 13:51:27 25 4
gpt4 key购买 nike

根据:

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

Under the new memory model, when thread A writes to a volatile variable V, and thread B reads from V, any variable values that were visible to A at the time that V was written are guaranteed now to be visible to 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。

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

最佳答案

更新:

对于感兴趣的任何人,此错误已在 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 版本和 CPU 下进行测试?

我使用的是 Windows 7,Java 1.6_24(尝试使用 _31)

编辑 2:

向 OP 和 Walter Laan 致敬 - 对我来说,这只是在我从 64 位 Java 切换到 32 位 Java 时才发生,在(但可能不排除在)64 位 Windows 7 上。

编辑 3:

tt 的赋值,或者更确切地说是 b 的 staticget 似乎有很大的影响(证明这删除了 ​​int tt = b;,它应该始终有效。

似乎将 b 加载到 tt 将在本地存储该字段,然后在 if 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

在此示例中,第一个条目来自打印“错误”的运行,而第二个来自未打印的运行。

在测试它等于 0 之前,工作运行似乎正确加载并分配了 b

  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%。

关于java - 为什么 java 5+ 中的 volatile 不能确保来自另一个线程的可见性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10620680/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com