<address id="xhxt1"><listing id="xhxt1"></listing></address><sub id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></sub>

    <thead id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></thead>

    volatile是否能保证数组中元素的可见性?

    在javaeye有位朋友问了我一个非常好的问题。

    问题

    一个线程向volatile的数组中设置值,而另一个线程向volatile的数组中读取。
    比如seg.setValue(2),随后另一个线程调用seg.getValue(2),前一个线程设置的值对读取的线程是可见的吗?

    我看书上说volatile的数组只针对数组的引用具有volatile的语义,而不是它的元素。

    ConcurrentHashMap中也有这样的代码,我很疑惑,希望得到你的解答,谢谢。

    public class Seg {
    
    private volatile Object[] tabs = new Object[10];
    
    public void setValue(int index) {
    tabs[index] = new Object();
    }
    
    public Object getValue(int index) {
    return tabs[index];
    }
    }
    
    

    我的回答

    我做了实验证实这句话是正确的,“volatile的数组只针对数组的引用具有volatile的语义,而不是它的元素”。

    测试代码如下:
    private static volatile Object[] tabs = new Object[10];

    public static void main(String[] args) {
    tabs[0]=1;

    tabs=new Object[10];
    }

    编译成汇编语句如下

    Java HotSpot(TM) Client VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
    CompilerOracle: compileonly *ArrayTest.main
    Loaded disassembler from D:\java\jdk1.6.0_33\jre\bin\client\hsdis-i386.dll
    Decoding compiled method 0x009ec7c8:
    Code:
    [Disassembling for mach='i386']
    [Entry Point]
    [Verified Entry Point]
    [Constants]
    # {method} 'main' '([Ljava/lang/String;)V' in 'volatileTest/ArrayTest'
    # parm0: ecx = '[Ljava/lang/String;'
    # [sp+0x30] (sp of caller)
    0x009ec8e0: mov %eax,-0x3000(%esp)
    0x009ec8e7: push %ebp
    0x009ec8e8: sub $0x28,%esp
    0x009ec8eb: mov $0x32aa1eb0,%esi ; {oop('volatileTest/ArrayTest')}
    0x009ec8f0: mov 0x150(%esi),%edi ;*getstatic tabs
    ; - volatileTest.ArrayTest::main@0 (line 7)
    0x009ec8f6: mov $0x1,%ecx ;*invokestatic valueOf
    ; - volatileTest.ArrayTest::main@5 (line 7)
    0x009ec8fb: mov %esi,0x14(%esp)
    0x009ec8ff: mov %edi,0x10(%esp)
    0x009ec903: call 0x009ad340 ; OopMap{[20]=Oop [16]=Oop off=40}
    ;*invokestatic valueOf
    ; - volatileTest.ArrayTest::main@5 (line 7)
    ; {static_call}
    0x009ec908: mov 0x10(%esp),%edi
    0x009ec90c: lea 0xc(%edi),%ebx
    0x009ec90f: cmpl $0x0,0x8(%edi) ; implicit exception: dispatches to 0x009eca07
    0x009ec916: jbe 0x009eca11
    0x009ec91c: cmp $0x0,%eax
    0x009ec91f: je 0x009ec960
    0x009ec925: mov 0x4(%edi),%edx ; implicit exception: dispatches to 0x009eca1d
    0x009ec928: mov 0x4(%eax),%esi
    0x009ec92b: mov 0x88(%edx),%edx
    0x009ec931: cmp %edx,%esi
    0x009ec933: je 0x009ec960
    0x009ec939: mov 0x10(%edx),%edi
    0x009ec93c: cmp (%esi,%edi,1),%edx
    0x009ec93f: je 0x009ec960
    0x009ec945: cmp $0x14,%edi
    0x009ec948: jne 0x009eca22
    0x009ec94e: push %esi
    0x009ec94f: push %edx
    0x009ec950: call 0x009ebb40 ; {runtime_call}
    0x009ec955: pop %esi
    0x009ec956: pop %edx
    0x009ec957: cmp $0x0,%edx
    0x009ec95a: je 0x009eca22
    0x009ec960: mov %eax,(%ebx)
    0x009ec962: shr $0x9,%ebx
    0x009ec965: movb $0x0,0x28eb100(%ebx) ;*aastore
    ; - volatileTest.ArrayTest::main@8 (line 7)
    0x009ec96c: mov $0xa,%ebx
    0x009ec971: mov $0x37a0e380,%edx ; {oop('java/lang/Object'[])}
    0x009ec976: mov %ebx,%edi
    0x009ec978: cmp $0xffffff,%ebx
    0x009ec97e: ja 0x009eca27
    0x009ec984: mov $0x13,%esi
    0x009ec989: lea (%esi,%ebx,4),%esi
    0x009ec98c: and $0xfffffff8,%esi
    0x009ec98f: mov %fs:0x0(,%eiz,1),%ecx
    0x009ec997: mov -0xc(%ecx),%ecx
    0x009ec99a: mov 0x34(%ecx),%eax
    0x009ec99d: lea (%eax,%esi,1),%esi
    0x009ec9a0: cmp 0x3c(%ecx),%esi
    0x009ec9a3: ja 0x009eca27
    0x009ec9a9: mov %esi,0x34(%ecx)
    0x009ec9ac: sub %eax,%esi
    0x009ec9ae: movl $0x1,(%eax)
    0x009ec9b4: mov %edx,0x4(%eax)
    0x009ec9b7: mov %ebx,0x8(%eax)
    0x009ec9ba: sub $0xc,%esi
    0x009ec9bd: je 0x009ec9e3
    0x009ec9c3: xor %ebx,%ebx
    0x009ec9c5: shr $0x3,%esi
    0x009ec9c8: jae 0x009ec9d8
    0x009ec9ce: mov %ebx,0xc(%eax,%esi,8)
    0x009ec9d2: je 0x009ec9e3
    0x009ec9d8: mov %ebx,0x8(%eax,%esi,8)
    0x009ec9dc: mov %ebx,0x4(%eax,%esi,8)
    0x009ec9e0: dec %esi
    0x009ec9e1: jne 0x009ec9d8
    0x009ec9e3: mov 0x14(%esp),%esi
    0x009ec9e7: mov %eax,0x150(%esi)
    0x009ec9ed: shr $0x9,%esi
    0x009ec9f0: movb $0x0,0x28eb100(%esi)
    0x009ec9f7: lock addl $0x0,(%esp) ;*putstatic tabs
    ; - volatileTest.ArrayTest::main@14 (line 9)
    0x009ec9fc: add $0x28,%esp
    0x009ec9ff: pop %ebp
    0x009eca00: test %eax,0x950100 ; {poll_return}
    0x009eca06: ret
    0x009eca07: call 0x009ea4d0 ; OopMap{[20]=Oop eax=Oop edi=Oop off=300}
    ;*aastore
    ; - volatileTest.ArrayTest::main@8 (line 7)
    ; {runtime_call}
    0x009eca0c: call 0x009ea4d0 ; OopMap{[20]=Oop eax=Oop edi=Oop off=305}
    ;*aastore
    ; - volatileTest.ArrayTest::main@8 (line 7)
    ; {runtime_call}
    0x009eca11: movl $0x0,(%esp)
    0x009eca18: call 0x009ea1d0 ; OopMap{[20]=Oop eax=Oop edi=Oop off=317}
    ;*aastore
    ; - volatileTest.ArrayTest::main@8 (line 7)
    ; {runtime_call}
    0x009eca1d: call 0x009ea4d0 ; OopMap{[20]=Oop eax=Oop off=322}
    ;*aastore
    ; - volatileTest.ArrayTest::main@8 (line 7)
    ; {runtime_call}
    0x009eca22: call 0x009eb850 ; OopMap{[20]=Oop eax=Oop off=327}
    ;*aastore
    ; - volatileTest.ArrayTest::main@8 (line 7)
    ; {runtime_call}
    0x009eca27: call 0x009eb1c0 ; OopMap{[20]=Oop off=332}
    ;*anewarray
    ; - volatileTest.ArrayTest::main@11 (line 9)
    ; {runtime_call}
    0x009eca2c: jmp 0x009ec9e3
    0x009eca2e: nop
    0x009eca2f: nop
    0x009eca30: mov %fs:0x0(,%eiz,1),%esi
    0x009eca38: mov -0xc(%esi),%esi
    0x009eca3b: mov 0x184(%esi),%eax
    0x009eca41: movl $0x0,0x184(%esi)
    0x009eca4b: movl $0x0,0x188(%esi)
    0x009eca55: add $0x28,%esp
    0x009eca58: pop %ebp
    0x009eca59: jmp 0x009bb5c0 ; {runtime_call}
    0x009eca5e: hlt
    0x009eca5f: hlt
    [Stub Code]
    0x009eca60: nop ; {no_reloc}
    0x009eca61: nop
    0x009eca62: mov $0x0,%ebx ; {static_stub}
    0x009eca67: jmp 0x009eca67 ; {runtime_call}
    [Exception Handler]
    0x009eca6c: call 0x009eb600 ; {runtime_call}
    0x009eca71: push $0x6db038d4 ; {external_word}
    0x009eca76: call 0x009eca7b
    0x009eca7b: pusha
    0x009eca7c: call 0x6da09de0 ; {runtime_call}
    0x009eca81: hlt
    [Deopt Handler Code]
    0x009eca82: push $0x9eca82 ; {section_word}
    0x009eca87: jmp 0x009ad970 ; {runtime_call}
    

     

    可以看到line 7给数组的某个元素赋值时没有lock前缀的指令。
    而修改数组line 9才有lock前缀的指令。

    0x009ec9f7: lock addl $0x0,(%esp) ;*putstatic tabs
    ; - volatileTest.ArrayTest::main@14 (line 9)
    

    原创文章,转载请注明: 转载自并发编程网 – www.gofansmi6.com本文链接地址: volatile是否能保证数组中元素的可见性?


    FavoriteLoading添加本文到我的收藏
    • Trackback 关闭
    • 评论 (12)
      • bells
      • 2013/01/21 9:40上午

      文章中汇编语句是如何生成的?
      我用javap生成的字节码 看不出区别来

    1. 这个问题有意思,再发散下,想到了另一个问题,如下代码:

      public class Seg {
       
      	private volatile Object[] tabs = new Object[10];
       
      	public void setValue(int index) {
      		tabs[index] = new Object();
      		tabs = tabs;
      	}
       
      	public Object getValue(int index) {
      		return tabs[index];
      	}
      }
      

      如上在set中有个对volatile引用的一个赋值操作,当然这句代码有可能被当做无用代码消除掉,但这句volatile store应该是有着内存语义的,如果真的消除这句代码,根据http://www.gofansmi6.com/jmm-cookbook-mb/中的第一段这样的描述“但在多核处理器上通常需要使用内存屏障指令来确保这种一致性。即使编译器优化掉了一个字段访问(例如,因为一个读入的值未被使用),这种情况下还是需要产生内存屏障,就好像这个访问仍然需要?;??!?br /> 那么我猜想,假设做set的操作只有一个线程(多个线程就存在数据争用了),那么这样给数组元素赋值在另一个线程就是可见的。

      • 如果get和set跑在不同的CPU里,我觉得应该是不可见的。因为tabs[index]和tabs应该使用的不同的缓存行。tabs的volatile写操作只能保证访问tabs的引用地址是可见。不能保证访问tabs[index]的引用可见。

        但是我觉得像CopyOnWriteArrayList把数组复制一份就可以可见,因为它们会加载到新的缓存里。

        tabs = new Object[10];
        tabs[index] = new Object();
        tabs = tabs;
        
        • 细想了下,确实不行,我是想通过happens-before关系来分析,但是之前的认识有点错误,找了些资料:

          资料1:JLS 第17章中的
          Two actions can be ordered by a happens-before relationship. If one action happens-before another, then the first is visible to and ordered before the second

          If we have two actions x and y, we write hb(x, y) to indicate that x happens-before
          y.

          When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race.

          Two accesses to (reads of or writes to) the same variable are said to be conflicting if at least one of the accesses is a write.

          资料2:《Java Concurrency in practice》16.1.4. Piggybacking on Synchronization。

          资料3:http://www.gofansmi6.com/happens-before/ 的最后一个例子。

          上面改过后的代码,如果轨迹是这样的:
          a.t1读取数组引用
          b.t1写入数组元素
          c.t1写入数组引用
          d.t2读取数组引用
          e.t2读取数组元素
          上面这个轨迹中有hb(c,d),在根据程序顺序规则以及hb的传递性,可以得到hb(b,e),这样t1写入数组的元素对t2就是可见的。

          但是,可能存在这样的一种轨迹:
          a.t2读取数组引用
          b.t1读取数组引用
          c.t1写入数组元素
          d.t1写入数组引用
          e.t2读取数组元素

          这个轨迹中c和e之间存在数据争用,也没有happens-before关系,因此,可见性也就无法保证。

        • michaelchan
        • 2016/03/16 9:47下午

        public class Seg {
        private volatile Object[] tabs = new Object[10];

        public void setValue(int index) {
        tabs[index] = new Object();
        tabs = tabs;
        }

        public Object getValue(int index) {
        return getArray()[index];
        }

        private Object[] getArray() {
        return tabs;
        }
        }

        这样子就是正确的,你看呢?

      • ryo
      • 2014/08/27 9:57下午

      LZ分析的是对的。

      • ryo
      • 2014/08/27 9:59下午

      所以,在很多源代码中都是直接用新对象替换老对象。

      • windranger
      • 2018/01/24 3:11下午

      那么如何让数组元素具有可见性呢?感觉这应该是个挺常有的需求啊。想来想去,数组应该没法让它满足这个需求了,我们没法对它的元素进行修饰volatile,百度也找不到答案。好在用英语在谷歌搜到了stackoverflow有同样的问题,有回答提出AtomicReferenceArray,nice!

      • longguofeng
      • 2019/06/12 10:25上午

      你好,我想问一个类似的问题,volatile修饰对象,能否保证对象内所有属性的可见行,期待您的答复。
      同时网上有个例子程序让我很疑惑。

      /**
      * 利用可见性控制并发,循环打印
      */
      public class VolatileTest {

      static Flag flag = new Flag();

      public static void main(String[] args) {
      new Thread(() -> {
      while (true) {
      if (“A”.equals(flag.flag)) {
      System.out.println(“A”);
      flag.flag = “B”;
      }
      }
      }).start();
      new Thread(() -> {
      while (true) {
      if (“B”.equals(flag.flag)) {
      System.out.println(“B”);
      flag.flag = “A”;
      }
      }
      }).start();

      while (true) {}
      }
      }

      class Flag {
      String flag = “A”;
      }

      • longguofeng
      • 2019/06/12 10:29上午

      longguofeng :
      你好,我想问一个类似的问题,volatile修饰对象,能否保证对象内所有属性的可见性,期待您的答复。
      同时网上有个例子程序让我很疑惑。
      /**
      * 利用可见性控制并发,循环打印
      */
      public class VolatileTest {
      static Flag flag = new Flag();
      public static void main(String[] args) {
      new Thread(() -> {
      while (true) {
      if (“A”.equals(flag.flag)) {
      System.out.println(“A”);
      flag.flag = “B”;
      }
      }
      }).start();
      new Thread(() -> {
      while (true) {
      if (“B”.equals(flag.flag)) {
      System.out.println(“B”);
      flag.flag = “A”;
      }
      }
      }).start();
      while (true) {}
      }
      }
      class Flag {
      String flag = “A”;
      }

      疑惑点在于把static Flag flag = new Flag(); 换成 static volatile Flag flag = new Flag(); 程序的表现确实不一样

    您必须 登陆 后才能发表评论

    return top

    爱投彩票 0ce| wu0| wa0| gwa| a9q| gcs| 9ue| ky9| mio| g9q| sgm| 9wo| kq9| mku| k00| oc8| uwe| y8w| occ| 8iq| ig8| sye| u8u| yck| 9qg| ge9| esk| g7y| w7a| uiu| 7ke| ag7| wkk| w8u| mqk| 8ug| iu8| wks| o8u| soi| 6os| scm| am7| 7oa| cg7| quc| w7y| cow| 7ck| ae7| swe| w5e| wkc| 6em| iwo| cy6| amu| u6a| kgy| 6uw|