锁升级的过程:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
- 无锁:当线程不持有该对象的锁时,对象处于无锁状态
- 偏向锁:判断对象头的MarkWord里的ThreadID是否为空,如果为空,尝试用CAS替换为自己的ThreadID,替换成功则表示锁获取成功,锁偏向该线程;后续通过检查对象头中MarkWord的线程id,如果一致,那么后面执行同步代码块,不需要通过CAS进行加锁解锁 a. 优点:适用于一个线程反复获得同一锁的情况,可以提高带有同步但竞争小的程序性能 b. 缺点:如果存在竞争会带来额外的锁撤销操作
- 重新偏向:其他线程尝试获取锁,判断Markword的线程id是否是自己的,不是则尝试用CAS将线程id修改为自己的id,修改成功表示发生重新偏向,锁不会升级
- 轻量级锁:修改失败(线程还在方法内执行),通过ThreadID找到对应的线程,通知其暂停运行,将锁标识设置为00,升级为轻量级锁 a. 轻量级锁的优点:绝大部分的锁在整个生命周期内都是不会存在竞争,在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗
- 轻量级锁的竞争:线程将对象头的Markword赋值到自己的栈帧,尝试用CAS将对象头的Markword替换为指向当前线程栈中锁记录的指针,成功表示抢到锁了;失败则自旋重试 a. 自旋锁的优点:竞争的线程不会阻塞挂起,提高了程序响应速度。避免重量级锁引起的性能消耗。 b. 自旋锁的缺点:占用CPU,如果始终获取不到锁,会升级为重量级锁
- 重量级锁:没有竞争到锁的线程会被park挂起,释放锁的时候会通过unpark唤醒阻塞的线程,唤醒涉及到操作系统的调度会有额外的开销
- 重量级锁的竞争: a. 尝试通过CAS将锁监视器的own修改为当前线程 b. 如果修改前的own是当前线程,说明发生锁重入,执行 recursions ++ , 记录重入的次数 c. 修改失败,则等待锁的释放重新竞争
64 位虚拟机 Mark Word 是 64bit 其结构如下:
对象头: