Java-锁升级
偏向锁/轻量级锁/重量级锁

锁

锁升级
JDK较早版本 OS的资源 互斥量 用户态->内核态的转换 重量级 效率比较低
现代版进行优化
无锁->偏向锁->轻量锁(自旋锁)->重量级锁


只有偏向锁1bit那个标记,偏向才有意义:1 可偏向、2 不可偏向
偏向锁
把当前线程指针贴到markword上
什么是偏向锁?
只偏向一个线程。markword上记录当前线程指针,下次同一个线程加锁的时候,不需要争用,只需要判断线程指针是否同一个,所以,偏向锁,偏向加锁的第一个线程,hashCode备份在线程栈上,线程销毁,锁降级为无锁
偏向锁默认是启动的,当JVM启动后,会延迟4s,偏向锁才启动。
为什么?
因为JVM启动时,内部好多方法需要上锁同步的过程,例如加载class过程,需要对某些内存进行同步。
偏向锁不可重偏向、批量偏向、批量撤销
为何会有偏向锁?
- 多数synchronized方法,在很多情况下,只有一个线程在运行
- 例如
- StringBuffer中的一些sync方法
- Vector中的一些sync方法
偏向锁的获取流程
获取流程
- 查看Mark Word中偏向锁的标识以及锁标志位,若是否偏向锁为1且锁标志位为01,则该锁为可偏向状态。
- 若为可偏向状态,则测试Mark Word中的线程ID是否与当前线程相同,若相同,则直接执行同步代码,否则进入下一步。
- 当前线程通过CAS操作竞争锁,若竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行同步代码,若竞争失败,进入下一步。
- 当前线程通过CAS竞争锁失败的情况下,说明有竞争。当到达全局安全点时之前获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。
偏向锁的释放流程
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁状态的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销需要等待全局安全点(即没有字节码正在执行),它会暂停拥有偏向锁的线程,撤销后偏向锁恢复到未锁定状态或轻量级锁状态。
可参考知了好学
偏向锁什么时候升级成轻量级锁?
只要有外来线程竞争,撤销偏向锁,升级轻量锁,线程在自己线程栈生成LockRecord,用CAS操作尝试将对象的markword设置为指向自己这个线程的Lock Record指针,设置成功者得到锁。
偏向锁什么时候升级成重量级锁?
当存在重度竞争(耗时过长的操作,wait等),会升级成重量级锁。
偏向锁的效率一定比轻量锁的高吗?
不一定。明确知道某个资源会有多个线程竞争,偏向锁肯定会涉及锁撤销,这时候直接使用自旋锁。JVM启动过程会有很多线程竞争(明确),所以默认情况启动时不打开偏向锁。
轻量级锁
什么是轻量级锁?
轻量级锁需要占用CPU资源。
轻量级锁是相对于重量级锁而言的,使用时不需要申请互斥量。而是在没有多线程竞争的情况下,使用轻量级锁能够减少性能消耗,但是当多个线程同时竞争锁时,轻量级锁会膨胀为重量级锁。
轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。
轻量级锁什么时候升级成重量级锁?
JDK1.6以前
- 自旋超过10次,升级为重量级锁,如果太多线程自旋CPU消耗过大,不如升级为重量级锁,进入等待队列(不消耗CPU),
- 超过CPU核数二分之一,升级重量级锁
JDK1.6以后
- 加入自适应自旋Adapative Self Spinning,JVM自己控制
- 升级重量锁:->向操作系统申请资源,linux mutex,CPU从3级-0级系统调用,线程挂起,进入等待队列。
重量级锁
什么是重量级锁?
重量级锁和轻量级锁的区别?
重量级锁不占用CPU资源,将线程放到等待队列waitSet
中,等待操作系统调度,再唤醒线程。这个过程算阻塞
。
为什么有自旋锁还需要重量级锁?
- 自旋是消耗CPU资源的,如果锁的时间长,或者自旋线程多,CPU会被大量消耗。
- 重量级锁有等待队列,所有拿不到锁的进入等待队列,不需要消耗CPU资源。
底层源码
synchronized编译成字节码后,可以看到一个monitorenter
和两个monitor
,这是因为包含了异常退出
查看JVM参数
java
查看XX开头的参数
java -XX:+PrintFlagsFinal -version

