synchronized锁的升级过程
对象头
锁对象可以是任何一个对象,使用对象头(Mark Word)来对控制哪个线程获得了当前这个锁对象。
无锁
锁对象刚被创建出来时没有线程竞争,此时对象处于无锁状态。根据jvm的设置,默认情况下无锁状态会在4秒后变为偏向锁,但存在以下例外
- 关闭偏向锁:无锁状态直接变为轻量级锁
- 关闭延迟:创建出的直接是匿名偏向锁
另外从上图中可以发现仅用无锁状态才有对象hashCode,因此如果要计算锁对象的identify hash code,其必须是无锁状态。
偏向锁
偏向锁的对象头中存有线程ID,其初始情况没有线程ID被称为匿名偏向锁。
在没有竞争的条件下,第一个获取锁的线程通过CAS将线程ID写入对象头中,后续只需要判断对象头中的线程ID和当前线程ID是否一致,就可以直接获得锁。
偏向锁不会主动释放,当有一个线程要获得偏向锁时,需要将对象头中的线程ID重新赋值,其只有两种情况可以赋值成功
- 该偏向锁是匿名偏向锁
- 该偏向锁是可重偏向状态
如果没有赋值成功,说明可能存在锁竞争,进入偏向锁撤销过程
偏向锁撤销
在全局安全点进行偏向锁撤销过程,其流程如下
注意点
偏向锁提高了单线程访问同步资源的性能,但如果同步资源一直在被多个线程竞争,那么撤销偏向锁这个过程是多余的,应该禁用偏向锁来避免偏向锁撤销过程中的STOP THE WORLD。
轻量级锁
从上图中可知,当出现线程竞争时偏向锁会升级为轻量级锁
轻量级锁的加锁过程
当锁对象处于无锁不可偏向状态时,线程首先在栈帧中创建一块“锁记录”,其中分为两个部分
- 一个部分用于存放锁对象Mark Word的拷贝
- 一个部分是指向锁对象的指针
要获得轻量级锁,首先将锁对象的Mark Word拷贝至锁记录中,然后使用CAS替换锁对象的Mark Word为指向锁记录的指针,然后将锁记录的指针也指向锁对象,此时线程就获得了轻量级锁。
如果CAS替换失败,检查锁对象的Mark Word指针
- 指向该线程:说明线程已经持有了锁,由于是可重复锁因此可以直接获得锁
- 指向其他线程:自旋等待,如果超过10次,升级为重量级锁
轻量级锁的释放
轻量级锁的释放过程就是将锁对象的Mark Word还原的过程,如果替换成功,那么轻量级锁正常被释放,如果替换失败,那么则是锁已经被升级为重量级锁,需要执行重量级锁的释放。
注意点
轻量级锁介于偏向锁和重量级锁,其利用CAS来避免频繁的内核态与用户态的切换,但如果CAS过多又会浪费CPU资源,因此需要在到达一定次数后升级到重量级锁。
重量级锁
重量级锁依赖于对象中内部的Monitor,其调用的是操作系统层面的Mutex Lock来实现,在进行线程切换时需要从用户态切换到内核态。
当锁对象为重量级锁时,Mark Word中的指针指向的是堆中与锁对象关联的Monitor对象,Monitor的owner标识出当前哪个线程获得了锁,在初始时和锁被释放后,owner值均为null。
注意
线程竞争重量级锁本质是在竞争Monitor对象的所有权,如果获取失败,当前线程阻塞,等待其他线程解锁后唤醒,再次竞争锁对象。 synchronized锁采用什么形态要根据临界区大小和线程竞争的激烈程度综合考虑
偏向锁:通常情况下只有一个线程反复获得锁
轻量级锁:有线程竞争,但临界区小,其他的线程自旋等待获得锁的开销小于线程切换
重量级锁:多个线程同时竞争,或临界区大,导致自旋等待的开销大于线程切换
一个简易的synchronized锁变化过程的状态机如下图所示