JUC并发编程 -- synchronized 原理进阶之轻量级锁 & 锁膨胀

Posted Z && Y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程 -- synchronized 原理进阶之轻量级锁 & 锁膨胀相关的知识,希望对你有一定的参考价值。

1. 轻量级锁

  • 轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
  • 轻量级锁对使用者是透明的,即语法仍然是 synchronized(也就是在给对象加锁的时候,优先考虑使用轻量级锁去加锁,如果轻量级级锁加锁失败,才会考试使用重量级锁加锁
  • 轻量级锁不需要Monitor锁,只是用线程栈中的锁记录锁

假设有两个方法同步块,利用同一个对象加锁:

static final Object obj = new Object();
public static void method1() {
 synchronized( obj ) {
 // 同步块 A
 method2();
 }
}
public static void method2() {
 synchronized( obj ) {
 // 同步块 B
 }
}

加锁步骤:

  1. 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word。

  2. 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

  3. 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下:

  4. 如果 cas 失败,有两种情况:

    • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
    • 如果是自己执行了 synchronized 锁重入(当前线程再次给加锁状态的对象加锁),那么再添加一条 Lock Record 作为重入的计数
  5. 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一

  6. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

2. 锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

演示代码:

static Object obj = new Object();
public static void method1() {
 synchronized( obj ) {
 // 同步块
 }
}
  1. 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

  1. 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程:
    1. Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址
    2. 然后自己进入 Monitor 的 EntryList BLOCKED
    3. 说明: 10表示重量级锁

  1. 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程。Monitor 概念(Java 对象头 & 原理之 Monitor(锁))


以上是关于JUC并发编程 -- synchronized 原理进阶之轻量级锁 & 锁膨胀的主要内容,如果未能解决你的问题,请参考以下文章

JUC并发编程总结复盘

JUC 高并发编程

JUC 高并发编程

JUC 高并发编程

一文总结 JUC 并发编程

JUC并发编程 -- 避免临界区的竞态条件之synchronized 解决方案(同步代码块)