根据:
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 != 0
是a == 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
,或者说静态化b
似乎有很大的影响(以证明删除了int tt = b;
,它应该一直有效。
它出现的负载b
成tt
将本地存储的字段,它然后将在如果coniditonal被使用(参考到该值不tt
)。因此,如果b == 0
为true,则可能意味着to的本地存储为tt
0(这是将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)
对于那些对汇编html" target="_blank">程序有更多经验的人,请权衡:)
编辑4
应该是我的最后一个编辑,因为并发开发人员可以使用它,我在有和没有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的内存模型有关,模型采用缓存与主存的方式对变量进行操作,也就是说,每个线程都有自己的缓存空间,对变量的操作都是在缓存中进行的,之后再将修改后的值返回到主存中,这就带来了问题,有可能一个线程在将共享变量修改后,还没有来的及将缓存中的变量返回给主存中,另外一个线程就对共享变量
我正在尝试实现LZ77的快速版本,我有一个关于并发编程的问题要问你。 现在我有一个和一个,两者的长度相同。该程序执行以下操作: > 主线程写入所有缓冲区,然后通知线程并等待它们完成。 单个工作线程处理缓冲区的一部分,并将结果保存在结果保持器的同一部分中。工人部分是排他性的。之后,主线程会收到通知,工作人员会暂停。 当所有的工作都暂停时,主线程读取结果持有器中的数据并更新缓冲区,然后(如果需要)过程
下面的代码片段使用多个线程,使用AtomicInteger计算到1亿。我有10个Writer线程来模拟写争用,还有一个Reader线程来模拟读争用。作者和读者还共享一个易变的布尔变量作为毒药。 但是,只有读线程可以退出,而写线程不能退出。尽管将变量声明为volatile,但为什么读线程不能看到更新的值? 附言:如果我把布尔值包在一个对象中,它就会工作。