根据:
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。
不过我有时会收到印有“错误”。 这怎么可能?
更新:
对于任何有兴趣的这个bug已经被解决,并固定为Java 7u6建立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楼的窗口7。
编辑3:
分配给tt
,或者说的staticget b
似乎有显著的影响(证明这个删除int tt = b;
它应该总是工作。
它出现的负载b
为tt
将本地存储的字段,它然后将在如果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%的机会。
基于从下面JCiP提取物,我本来以为你的榜样不应该打印“错误”:
volatile变量的可视性效果超出volatile变量本身的价值。 当一个线程A向其写入的易失性可变,并随后线程B读取相同的变量,即,以写入到易失性可变读取挥发性可变后变得到B可见是可见的,以一种现有的所有变量的值。
你可能想看看并发兴趣邮件列表上讨论话题在这个问题上: http://cs.oswego.edu/pipermail/concurrency-interest/2012-May/009440.html
这似乎是这个问题更容易与客户端JVM(-client)再现。
在我看来,这个问题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;
}
}
}