深入Java底层:内存屏障与JVM并发详解(2)

  我们可以看到x86 Xeon在第11、12行执行两次volatile写操作。第二次写操作后面紧跟着mfence操作--显式的双向内存屏障,下面的连续写操作基于SPARC。

  16. 1 0xfb8ecc84: ldub  [ %l1 + 0x155 ], %l3  ;...e60c6155

  17. 2 0xfb8ecc88: cmp  %l3, 0               ;...80a4e000

  18. 3 0xfb8ecc8c: bne,pn   %icc, 0xfb8eccb0  ;...12400009

  19. 4 0xfb8ecc90: nop                       ;...01000000

  20. 5 0xfb8ecc94: st  %l0, [ %l1 + 0x150 ]  ;...e0246150

  21. 6 0xfb8ecc98: clrb  [ %l1 + 0x154 ]     ;...c02c6154

  22. 7 0xfb8ecc9c: membar  #StoreLoad        ;...8143e002

  23. 8 0xfb8ecca0: sethi  %hi(0xff3fc000), %l0  ;...213fcff0

  24. 9 0xfb8ecca4: ld  [ %l0 ], %g0          ;...c0042000

  25.10 0xfb8ecca8: ret                       ;...81c7e008

  26.11 0xfb8eccac: restore                   ;...81e80000

  我们看到在第五、六行存在两次volatile写操作。第二次写操作后面是一个membar指令--显式的双向内存屏障。x86和SPARC的指令流与Itanium的指令流存在一个重要区别。JVM在x86和SPARC上通过内存屏障跟踪连续写操作,但是在两次写操作之间没有放置内存屏障。

  另一方面,Itanium的指令流在两次写操作之间存在内存屏障。为何JVM在不同的硬件架构之间表现不一?因为硬件架构都有自己的内 存模型,每一个内存模型有一套一致性保障。某些内存模型,如x86和SPARC等,拥有强大的一致性保障。另一些内存模型,如Itanium、 PowerPC和Alpha,是一种弱保障。

  例如,x86和SPARC不会重新排序连续写操作--也就没有必要放置内存屏障。Itanium、 PowerPC和Alpha将重新排序连续写操作--因此JVM必须在两者之间放置内存屏障。JVM使用内存屏障减少Java内存模型和硬件内存模型之间的距离。

  隐式内存屏障

  显式屏障指令不是序列化内存操作的唯一方式。让我们再看一看Counter类这个例子。

  27.class Counter{

  28.

  29.    static int counter = 0;

  30.

  31.    public static void main(String[] _){

  32.        for(int i = 0; i < 100000; i++)

  33.            inc();

  34.    }

  35.

  36.    static synchronized void inc(){ counter += 1; }

  37.

  38.}

  Counter类执行了一个典型的读-修改-写的操作。静态counter字段不是volatile的,因为所有三个操作必须要原子可见的。因此,inc 方法是synchronized修饰的。我们可以采用下面的命令编译Counter类并查看生成的汇编指令。Java内存模型确保了synchronized区域的退出和volatile内存操作都是相同的可见性,因此我们应该预料到会有另一个内存屏障。

  39.$ java -XX:+UnlockDiagnosticVMOptions -XX:PrintAssemblyOptions=hsdis-print-bytes

  40.-XX:-UseBiasedLocking -XX:CompileCommand=print,Counter.inc Counter

  41. 1  0x04d5eda7: push   %ebp               ;...55

  42. 2  0x04d5eda8: mov    %esp,%ebp          ;...8bec

  43. 3  0x04d5edaa: sub    $0x28,%esp         ;...83ec28

  44. 4  0x04d5edad: mov    $0x95ba5408,%esi   ;...be0854ba 95

  45. 5  0x04d5edb2: lea    0x10(%esp),%edi    ;...8d7c2410

  46. 6  0x04d5edb6: mov    %esi,0x4(%edi)     ;...897704

  47. 7  0x04d5edb9: mov    (%esi),%eax        ;...8b06

  48. 8  0x04d5edbb: or     $0x1,%eax          ;...83c801

  49. 9  0x04d5edbe: mov    %eax,(%edi)        ;...8907

  50.10  0x04d5edc0: lock cmpxchg %edi,(%esi)  ;...f00fb13e

  51.11  0x04d5edc4: je     0x04d5edda         ;...0f841000 0000

  52.12  0x04d5edca: sub    %esp,%eax          ;...2bc4

  53.13  0x04d5edcc: and    $0xfffff003,%eax   ;...81e003f0 ffff

  54.14  0x04d5edd2: mov    %eax,(%edi)        ;...8907

  55.15  0x04d5edd4: jne    0x04d5ee11         ;...0f853700 0000

  56.16  0x04d5edda: mov    $0x95ba52b8,%eax   ;...b8b852ba 95

  57.17  0x04d5eddf: mov    0x148(%eax),%esi   ;...8bb04801 0000

  58.18  0x04d5ede5: inc    %esi               ;...46

  59.19  0x04d5ede6: mov    %esi,0x148(%eax)   ;...89b04801 0000

  60.20  0x04d5edec: lea    0x10(%esp),%eax    ;...8d442410

  61.21  0x04d5edf0: mov    (%eax),%esi        ;...8b30

  62.22  0x04d5edf2: test   %esi,%esi          ;...85f6

  63.23  0x04d5edf4: je     0x04d5ee07         ;...0f840d00 0000

  64.24  0x04d5edfa: mov    0x4(%eax),%edi     ;...8b7804

  65.25  0x04d5edfd: lock cmpxchg %esi,(%edi)  ;...f00fb137

  66.26  0x04d5ee01: jne    0x04d5ee1f         ;...0f851800 0000

  67.27  0x04d5ee07: mov    %ebp,%esp          ;...8be5

  68.28  0x04d5ee09: pop    %ebp               ;...5d

  不出意外,synchronized生成的指令数量比volatile多。第18行做了一次增操作,但是JVM没有显式插入内存屏障。相反,JVM通过在 第10行和第25行cmpxchg的lock前缀一石二鸟。cmpxchg的语义超越了本文的范畴。

  lock cmpxchg不仅原子性执行写操作,也会刷新等待的读写操作。写操作现在将在所有后续内存操作之前完成。如果我们通过java.util.concurrent.atomic.AtomicInteger 重构和运行Counter,将看到同样的手段。

  69. import java.util.concurrent.atomic.AtomicInteger;

  70.

  71.    class Counter{

  72.

  73.        static AtomicInteger counter = new AtomicInteger(0);

  74.

  75.        public static void main(String[] args){

  76.            for(int i = 0; i < 1000000; i++)

  77.                counter.incrementAndGet();

  78.        }

  79.

  80.    }

  81.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwpzyx.html