一、synchronized锁加到什么地方
synchronized 上锁,其实锁信息是加在对象头中的 markdown,对象中的前四个字节表示markdown;markdown还记录了对象的gc、hashcode 信息
注意:markdown 结构与jvm虚拟机的实现有关,
I)32位Hotspot虚拟机markdown结构如下:
II)64位Hotspot 虚拟机的markdown结构如下:
二、synchronized 锁升级的过程
synchronized 锁升级其实就是markword 后几位锁标志位变化的过程
当new 出一个对象时会有2种情况:普通对象(主线)和 匿名偏向
当new 出一个普通对象,一旦给普通对象加上关键字synchronized,synchronized 锁
会升级为偏向锁(),
当锁竞争加大(轻度竞争),偏向锁会升级为轻量级锁(也叫自旋锁或无锁)
当竞争超过一定时间还没获取锁,轻量级锁就会升级成重量级锁
锁状态:
在markdown 中最低3位表示synchronized 锁的状态,优先看最低的2位,
下边是markdown锁标志位与锁的对应关系
偏向锁标志位 | 锁标志位2 | 锁标志位1 | |
轻量级锁,自旋锁,无锁 | 0 | 0 | |
重量级锁 | 1 | 0 | |
GC标志位(用于垃圾回收) | 1 | 1 | |
偏向锁 | 1 | 0 | 1 |
无锁态(刚刚new出对象) | 0 | 0 | 1 |
三、用户空间锁 VS 重量级锁
偏向锁 、轻量级锁都是用户空间完成的,又称为用户锁,用户锁不用向cpu内核申请锁;
重量级锁是需要向操作系统内核(即CPU)申请锁
什么是偏向锁?
对很多synchronized 的方法或代码块,很多时候是在线程环境中执行的,没有竞争,
这就没必要每次执行synchronized方法都向CPU内核申请加锁;当第一个线程调
用 synchronized方法时,只需要把该线程的id设置到markdown 中就行了,这就是偏向锁;
什么是轻量级锁(自旋锁,无锁)?
有多个线程竞争synchronized锁时,先把偏向锁撤销,通过自旋的竞争(自旋锁)升级
到轻量级锁;
竞争过程:
每个线程的线程栈中都会生成一个LR(LockRecord 锁记录),然后通过自旋争抢
的方式,尽可能的把自身的LR设置到 markdown中,若设置成功,makrdown会有
一个指针指向LR,表示该线程持有了synchronized;竞争失败的线程会在用户空间
通过CAS的方式(自旋)继续竞争
什么是重量级锁?
重量级锁markdown 记录的是一个 ObjectMointor 对象;
ObjectMointor 是jvm提供的一个用于向操作系统OS申请锁的工具,由C++实现
四、synchronized 锁重入
synchronized 是可以重入的锁
可重入锁必须要记录重入次数,因为重入几次后边就要释放几次
偏向锁的重入次数记录在线程栈中,每重入一次 LockRecord 就要加1,
偏向锁每重入一次,在线程栈中都会生成一个LR,但这个LR的值等于NULL,重入次数
记录在最底层(即第一个LR)LR中;每释放一次锁,线程栈中都要弹出一个LR,当
线程栈中的LR全部弹出,表示锁全部释放
轻量级锁重入次数与偏向锁差不多,锁重入次数都是记录在线程栈的LR重,每重入一次也是
生成一个LR;
重量级锁的重入次数记录在ObjectMointor 对象中
五、轻量级锁(自旋锁)什么时候升级为重量级锁?
有线程超过10次自旋,或者自旋线程数超过cpu核数的一半;
可以通过jvm参数 -XX:PreBlockSpin 来控制自旋次数,jdk1.6之后不需要手动调整自旋次数
jdk1.6之后加入了自适应自旋 Adapative Self Spinning,由jvm自己控制
升级重量级锁:向操作系统申请资源,linux mutex,CPU从3级-0级系统调用,线程
挂起,进入等待队列等待系统调度,然后再映射会用户空间
六、什么有自旋锁了还需要重量级锁?
要明白一点,自旋是需要消耗cpu资源的,若持有锁的线程业务耗时过长或自旋的线程
比较多,这样会消耗大量的cpu资源;
重量级锁有等待队列,所有拿不到锁的线程会被放入等待队列中,不需要消耗cpu资源
七、什么叫偏向锁启动和偏向锁未启动?
偏向锁是否一定比自旋锁效率高?
不一定,偏向锁只有在单线程环境中效率最高;多线程环境下偏向锁不一定比自旋锁
效率高;明确知道多线程情况下没必要使用偏向锁,因为多线程环境下偏向锁一定涉及
锁撤销,这就额外增加了cpu消耗,此时应该关闭偏向锁,直接使用自旋算性能可能更高
jvm启动过程中,明确知道会有很多线程争抢,所以启东时默认不打开偏向锁,启东完成
后再打开偏向锁,即延时打开偏向锁
-XX:BiasedLockingStartupDelay 用来设置偏向锁延时