为什么volatile在Java 5+不同步缓存副本的变量与主内存? [英] Why volatile in java 5+ doesn't synchronize cached copies of variables with main memory?

查看:155
本文介绍了为什么volatile在Java 5+不同步缓存副本的变量与主内存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据:



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; //使jvm缓存的值为b

while(a == 0){

}

if(b == 0) {
System.out.println(error);
}
}

} .start();
}

b = 1;
a = 1;
}
}

b

$ <$>

$ <$>

我有时会收到错误打印。这是怎么回事?

解决方案

更新:



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





原始回答



当考虑内存可见性/顺序时,您需要考虑其发生之前的关系。 b!= 0 的重要前提条件是 a == 1 。如果 a!= 1 ,那么b可以是0或1.



c> a == 1 那么该线程保证看到 b == 1



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



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



什么操作系统,Java版本& CPU



我在Windows 7,Java 1.6_24(尝试_31)



编辑2:



感谢OP和Walter Laan - 对我来说,只有当我从64位Java切换到32位Java时,



$ b

$ b

tt ,或者 b 的静态作用似乎有重大影响证明这个删除 int tt = b; ,它应该总是工作。



c $ c> b 到 tt 将存储本地的字段,然后将用于if coniditonal因此,如果 b == 0 是真的,它可能意味着本地存储到 tt 为0(在这一点上它是一个比赛,将1分配给本地 tt )这似乎只对32位Java 1.6& 7与客户端设置。



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



p $ p> 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;隐式异常:分派到0x021dd7c2
0x021dd775:nop; * getstatic out
; - test $ 1 :: run @ 16(line 18)
0x021dd776:cmp(%ecx),%eax;隐式异常:分派到0x021dd7dc
0x021dd778:mov $ 0x39239500,%edx; * invokevirtual println



$ b

这未打印错误 code> 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;隐式异常:分派到0x0226d7f7
0x0226d795:nop; * getstatic out
; - test $ 1 :: run @ 16(line 18)
0x0226d796:cmp(%ecx),%eax;隐式异常:分派到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
; - 测试$ 1 :: run @ 13(行17)

缓存版本%edx

  0x021dd75e:cmp $ 0x0, edx 
0x021dd761:jne 0x021dd788; * ifne
; - 测试$ 1 :: run @ 13(行17)

对于有更多汇编经验的人重量:)



编辑4



并发开发人员得到了一个手,我做了测试和没有
int tt = b; 分配一些。我发现,当我将最大值从100增加到1000时,当包括 int tt = b 时,似乎有100%的错误率,


According to:

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

And many places on the internet state that the following code should never print "error":

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 should be 1 for all the threads when a is 1.

However I sometimes get "error" printed. How is this possible?

解决方案

Update:

For anyone interested this bug has been addressed and fixed for Java 7u6 build b14. You can see the bug report/fixes here

Original Answer

When thinking in terms of memory visibility/order you would need to think about its happens-before relationship. The important pre condition for b != 0 is for a == 1. If a != 1 then b can be either 0 or 1.

Once a thread sees a == 1 then that thread is guaranteed to see b == 1.

Post Java 5, in the OP example, once the while(a == 0) breaks out b is guaranteed to be 1

Edit:

I ran the simulation many number of times and didn't see your output.

What OS, Java version & CPU are you testing under?

I am on Windows 7, Java 1.6_24 (trying with _31)

Edit 2:

Kudos to the OP and Walter Laan - For me it only happened when I switched from 64 bit Java to 32 bit Java, on (but may not be excluded to) a 64 bit windows 7.

Edit 3:

The assignment to tt, or rather the staticget of b seems to have a significant impact (to prove this remove the int tt = b; and it should always work.

It appears the load of b into tt will store the field locally which will then be used in the if coniditonal (the reference to that value not tt). So if b == 0 is true it probably means that the local store to tt was 0 (at this point its a race to assign 1 to local tt). This seems only to be true for 32 Bit Java 1.6 & 7 with client set.

I compared the two output assembly and the immediate difference was here. (Keep in mind these are snippets).

This printed "error"

 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

And

This did not print "error"

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

In this example the first entry is from a run that printed "error" while the second was from one which didnt.

It seems that the working run loaded and assigned b correctly before testing it equal to 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)

While the run that printed "error" loaded the cached version of %edx

  0x021dd75e: cmp    $0x0,%edx
  0x021dd761: jne    0x021dd788         ;*ifne
                                        ; - Test$1::run@13 (line 17)

For those who have more experience with assembler please weigh in :)

Edit 4

Should be my last edit, as the concurrency dev's get a hand on it, I did test with and without the int tt = b; assignment some more. I found that when I increase the max from 100 to 1000 there seems to be a 100% error rate when int tt = b is included and a 0% chance when it is excluded.

这篇关于为什么volatile在Java 5+不同步缓存副本的变量与主内存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆