3. synchronized、CAS、volitale底层实现

synchronized、CAS、volitale底层实现

CAS

  1. Atomic类都使用CAS,它依赖于Unsafe类提供的compareAndSwap方法,如AtomicInteger依赖的是Unsafe的compareAndSwapInt方法,其是native的,实现在jvm源码unsafe.cpp中;

  2. unsafe.cpp中CompareAndSwapInt中是调用了Atomic::cmpxchg函数;

  3. 该函数在atomic_linux_x86_.inline.hpp中使用了汇编指令:

    使用了LOCK_IF_MP和cmpxchg

synchronized

  1. 编译时候:使用字节码monitorenter和monitorexit指令对实现;
  2. 运行时:JVM会对monitor做加锁优化,偏向锁-轻量级锁-重量级锁;
  3. lock 和cmpxchg汇编指令实现;

学会使用JOL

  1. 添加依赖:

     <dependency>
         <groupId>org.openjdk.jol</groupId>
         <artifactId>jol-core</artifactId>
         <version>0.9</version>
     </dependency>
    
  2. 使用ClassLayout:

     Object o = new Object();
     System.out.println(ClassLayout.parseInstance(o).toPrintable());
    
  3. 分析结果:

     java.lang.Object object internals:
      OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
           0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
           4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
           8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
          12     4        (loss due to the next object alignment)
     Instance size: 16 bytes
     Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    

普通对象(x64):

  • 0~8字节:markword
  • 8~12字节:class pointer (启动了压缩指针)
  • 以后的都是:实例数据;
  • 以8字节为单位(64位处理器):填充,loss due to the next object alignment,因为对齐而丢失的字节;

数组对象在class pointer和实例数据之间还有4个字节,表示数组的长度;

对象头信息:

64位:

64bitObjectHeader.jpg

32位:


32bitObjectHeader.jpg

锁升级(具体加锁对对象头产生的影响,看上面的图):

  1. 对象new出来,第一次加锁,偏向锁;
  2. 竞争时,将升级为轻量级锁;
  3. 竞争比较大,多次重试无法获取到锁或者很多线程都在尝试获取同一个轻量级锁,将升级为重量级锁;

锁消除:

简单,仅仅放个标题;

锁粗话:

简单,仅仅放个标题;

hsdis插件可以查看jvm生成的汇编码:

启动时候指定参数,将打印生成的汇编码(需要暗转hsdis插件):

java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly xxx; 

volitile:线程可见性、禁止重排序;

cpu超线程

内存中的数据要先加载到register中才能被cpu利用;

就是一个ALU单元 对应两组register和PC,这样省去了这两个线程之间切换时上下文切换的开销;

局部性原理:

当我们使用某个数据时,很有可能会使用相邻的数据;

cache line(64个字节)

cacheLine.jpg

缓存失效是以缓存行为单位的;

disruptor 环形队列,单机最快的队列,使用到了缓存行对齐,即让不同的volatile 变量存放在不同的缓存行中,提升效率;

MESI Cache一致性协议(X86 cpu):

每个缓存行有四种状态:

  1. Modified[图片上传中...(32bitObjectHeader.jpg-c9214c-1594222995296-0)]

  2. Exclusive

  3. Shared

  4. Invalid

跨越多个缓存行的数据,依然必须使用总线锁;

乱序执行:

int x,y,a,b;

new Thread(){
    run(){
        a=1;
        x=b;
    }
}.start();

new Thread(){
    run(){
        b=1;
        y=a;
    }
}.start();

如果没有乱序执行,那么x和y不会出现0和0的组合;

new 对象分几个步骤(加上指令重排序,所以DCL需要volatile)

  1. new 分配空间(这里的new类似于malloc);
  2. init方法(构造方法);
  3. astore_1();

volatile解决指令重排序

  1. 语言级别:volatile;

  2. 字节码:ACC_VOLATILE

  3. JVM:内存屏障,可以保证屏障前面的不会被重排序到后面去

    1. LoadLoad 屏障:屏障前Load操作和屏障后Load操作不能重排序,以下类似;

    2. StoreStore

    3. LoadStore

    4. StoreLoad

       //JVM保证volatile禁止指令重排序;
       StoreStoreBarrier
       volatile写操作;
       StoreLoadBarrier
      
       LoadLoadBarrier
       volatile读操作;
       LoadStoreBarrier
      
  4. hotspot:bytecodeinterpreter.cpp中实现:OrderAccess::fence();

  5. linux:orderaccess_linux_x86.inline.hpp还是lock实现的,没有指定对象,直接锁了总线;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。