吃透 synchronized 底层原理:从偏向锁到重量级锁的完整拆解

synchronized锁升级全解析

目录

前言

一、先明确核心前提:synchronized 的 “锁” 到底是什么?

二、synchronized 的底层基石:对象头与 Monitor

1. 对象头:锁状态的 “存储区”

2. Monitor:锁的 “管理员”

三、synchronized 的性能优化:锁升级机制(核心重点)

1. 偏向锁:无竞争时的 “专属锁”

2. 轻量级锁:低竞争时的 “自旋抢锁”

3. 重量级锁:高竞争时的 “阻塞排队”

四、synchronized 的核心特性总结

五、面试小贴士:如何清晰回答 synchronized 底层原理?


前言

在 Java 并发编程中,synchronized是最基础也最常用的同步手段,但它的底层原理(尤其是锁升级、轻量级锁的 CAS 竞争逻辑)往往让初学者困惑。本文结合我在学习过程中与AI的对话,完整梳理synchronized的底层逻辑,重点突破轻量级锁的核心难点,希望能够帮助到大家。

一、先明确核心前提:synchronized 的 “锁” 到底是什么?

synchronized的本质是 “让多线程排队访问共享资源”,而实现这一逻辑的核心载体是 “锁对象”—— 任何 Java 对象(如new Object()、类对象、实例对象)都自带一把 “锁”,synchronized正是通过 “争抢这把锁” 控制线程同步。

关键结论:

  • 当你写synchronized(lockObj) { ... }时,争抢的是lockObj这把 “锁”;
  • 当你用synchronized修饰实例方法时,争抢的是this(当前实例)这把 “锁”;
  • 当你用synchronized修饰静态方法时,争抢的是 “类对象(如 Xxx.class)” 这把 “锁”。

二、synchronized 的底层基石:对象头与 Monitor

要理解锁的实现,必须先搞懂两个核心结构:对象头Monitor(管程)—— 它们是synchronized实现同步的 “硬件基础”。

1. 对象头:锁状态的 “存储区”

每个 Java 对象在内存中都包含三部分:对象头 + 实例数据(成员变量等) + 对齐填充。其中,对象头是锁状态的 “记录本”,而synchronized锁定的 “锁对象” 的对象头,正是存储锁信息的关键。

对象头中有一个核心区域叫Mark Word(标记字段),它的结构会随 “锁状态” 动态变化(以 32 位 JVM 为例):

锁状态Mark Word 存储内容核心作用
无锁对象哈希码 + 分代年龄记录对象基本信息
偏向锁偏向的线程 ID + 分代年龄标记 “锁偏爱哪个线程”
轻量级锁指向线程栈中 “锁记录(Lock Record)” 的指针关联持有锁的线程
重量级锁指向 “Monitor(管程)” 的指针交给管理员管理线程竞争

关键疑问解答:对象头指的是 “锁对象的对象头吗?”是的!只有被synchronized锁定的 “锁对象”,其对象头的 Mark Word 才会记录锁状态 —— 离开锁对象的对象头,锁的状态管理、线程竞争都无从谈起。

2. Monitor:锁的 “管理员”

每个 Java 对象都隐式关联一个Monitor(可理解为 “锁的管理员”,由 C++ 实现,如 HotSpot 中的ObjectMonitor),它负责管理线程的 “抢锁” 和 “排队”,内部包含三个核心结构:

  • Owner:记录当前持有锁的线程(初始为 null);
  • EntryList:未抢到锁的线程排队的队列(线程阻塞状态);
  • WaitSet:调用wait()后暂时 “离场” 的线程队列(需notify()唤醒后重新排队);
  • Recursion Count:锁的重入计数(同一线程多次抢锁时累加,减到 0 才释放)。

Monitor 的核心逻辑:当线程尝试抢锁时,会向 Monitor “申请权限”:

  1. 若 Owner 为 null,线程直接成为 Owner(Recursion Count=1);
  2. 若 Owner 是当前线程,Recursion Count+1(可重入);
  3. 若 Owner 是其他线程,当前线程进入 EntryList 阻塞,等待 Owner 释放锁。

三、synchronized 的性能优化:锁升级机制(核心重点)

JDK 1.6 之前,synchronized直接使用 “重量级锁”(依赖操作系统互斥量,线程阻塞 / 唤醒开销大),性能较差。JDK 1.6 后引入 “锁升级机制”—— 根据 “竞争激烈程度” 动态切换锁状态,平衡 “安全性” 和 “性能”。

锁升级的完整路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(不可逆,只能升级不能降级)。

1. 偏向锁:无竞争时的 “专属锁”

核心场景

只有一个线程反复获取锁(无其他线程竞争),比如单线程执行同步代码。

实现逻辑

  • 线程 A 第一次抢锁时,JVM 通过 CAS 操作,将 “线程 A 的 ID” 写入锁对象的 Mark Word(Mark Word 进入 “偏向锁状态”
  • 线程 A 之后再抢锁,只需检查 Mark Word 中的线程 ID 是否为自己:若是,直接进入代码(无需任何 CAS 操作,几乎零开销);
  • 若有其他线程(如 B)尝试抢锁,偏向锁会被 “撤销”,升级为轻量级锁。

关键细节:偏向锁有锁记录吗?

没有!锁记录(Lock Record)是轻量级锁的结构,而偏向锁的设计目标是 “无竞争场景下的极致简化”—— 直接通过 Mark Word 记录线程 ID,无需在线程栈中创建额外结构。

2. 轻量级锁:低竞争时的 “自旋抢锁”

核心场景

少量线程竞争锁,且每个线程持有锁的时间短(比如线程执行同步代码仅需几微秒)。

核心逻辑

当偏向锁被撤销(有线程 B 参与竞争),锁升级为轻量级锁,此时锁对象的 Mark Word 存储 “指向持有锁线程栈中锁记录的指针”(比如指向线程 A 的锁记录)。

下面结合 “线程 A 持有锁,线程 B 抢锁” 的完整流程,拆解轻量级锁的核心难点:

步骤 1:线程 A 如何持有轻量级锁?

  1. 线程 A 执行同步代码前,在自己的 “线程栈”中创建一块空间 ——锁记录(Lock Record)
  2. 线程 A 将锁对象当前的 Mark Word(此时可能是偏向锁状态或无锁状态)拷贝到自己的锁记录中;
  3. 线程 A 通过 CAS 操作,将锁对象的 Mark Word 改为 “指向自己锁记录的指针”——CAS 成功,线程 A 持有轻量级锁,进入代码执行。

步骤 2:线程 B 如何 “自旋 + CAS” 抢锁?

当线程 B 执行到同步代码时,发现锁被 A 持有(Mark Word 指向 A 的锁记录),会启动 “自旋抢锁” 流程:

  1. 创建锁记录:线程 B 在自己的栈中创建锁记录,拷贝当前 Mark Word(即 “指向 A 锁记录的指针”),并记录锁对象地址;
  2. CAS 尝试抢锁:线程 B 通过 CAS,尝试将锁对象的 Mark Word 改为 “指向自己锁记录的指针”—— 此时分两种情况:

情况 1:A 仍在执行代码(未释放锁),B 的 CAS “表面成功但实际无效”

  • B 的 CAS 看似成功(Mark Word 改成了指向 B 的锁记录),但 JVM 会强制 B 做 “归属权检查”;
  • B 通过自己锁记录中 “拷贝的 Mark Word”(指向 A 的锁记录的指针),找到 A 的锁记录,发现 A 的锁记录中 “持有线程” 仍为 A(A 还在执行代码);
  • JVM 判定 B 的 CAS 无效,将 Mark Word 回滚为 “指向 A 的锁记录”,B 继续自旋重试(默认自旋 10 次,避免立即阻塞)。

情况 2:A 准备释放锁,B 的 CAS “真正成功”

  • A 执行完代码后,先 “放弃归属权”:将自己锁记录中的 “持有线程” 改为 null
  • A 开始执行 “释放 CAS”(试图将 Mark Word 改回原始状态),但 B 的 CAS 比 A 快一步,先将 Mark Word 改为 “指向自己的锁记录”;
  • JVM 强制 B 做归属权检查:B 通过指针找到 A 的锁记录,发现 “持有线程” 为 null(A 已释放);
  • JVM 判定 B 的 CAS 有效,B 在自己的锁记录中标记 “持有线程为 B”,成功获锁并执行代码。

步骤 3:轻量级锁何时升级为重量级锁?

若线程 B 自旋到阈值(如 10 次)仍未抢到锁,或有更多线程(如 C、D)参与竞争,JVM 会判定 “竞争超出轻量级锁的承载能力”,触发锁膨胀

  1. 将锁对象的 Mark Word 改为 “指向 Monitor 的指针”(轻量级锁→重量级锁);
  2. 线程 B 停止自旋,进入 Monitor 的 EntryList 阻塞,等待管理员叫号(A 释放锁后,Monitor 唤醒队列中的线程)。

轻量级锁的关键特性

  • 可重入性:线程持有锁后,进入嵌套同步代码时,只需将锁记录的 “重入计数”+1,无需再次 CAS;
  • 自旋阈值:默认由 JVM 控制(如 10 次或 CPU 核心数的一半),避免无意义的 CPU 空转;
  • 安全保障:通过 “归属权检查” 确保同一时间只有一个线程持有锁,不会出现 “两个线程同时执行同步代码” 的问题。

3. 重量级锁:高竞争时的 “阻塞排队”

核心场景

多线程激烈竞争锁,或线程持有锁的时间长(比如同步代码中包含 IO 操作)。

实现逻辑

  • 锁对象的 Mark Word 指向 Monitor(管理员);
  • 线程抢锁时,若 Owner 已被占用,直接进入 EntryList 阻塞(放弃 CPU,不再自旋);
  • 持有锁的线程释放锁后,Monitor 从 EntryList 中唤醒一个线程,将 Owner 交给它(非公平,新线程可能插队)。

特点

  • 优点:避免自旋浪费 CPU,适合长时间持有锁或高竞争场景;
  • 缺点:线程阻塞 / 唤醒需要切换内核态,开销比轻量级锁大。

四、synchronized 的核心特性总结

  1. 可重入性:同一线程可多次获取同一把锁(通过 Monitor 的 Recursion Count 实现),避免自己阻塞自己;
  2. 非公平性:锁释放后,等待队列中的线程不保证按顺序获锁(新线程可能直接抢占,提升吞吐量);
  3. 渐进式优化:通过 “偏向锁→轻量级锁→重量级锁” 的升级,动态适配竞争强度,平衡安全与性能。

五、面试小贴士:如何清晰回答 synchronized 底层原理?

建议按 “载体→机制→优化” 的逻辑回答,结合通俗比喻:

  1. 先讲核心载体:synchronized基于 “锁对象” 实现,锁的状态存在 “锁对象的对象头(Mark Word)” 中,由 “Monitor” 管理线程竞争;
  2. 再讲锁升级机制:从偏向锁(无竞争,记线程 ID)到轻量级锁(低竞争,自旋 + CAS),再到重量级锁(高竞争,阻塞排队);
  3. 重点拆解轻量级锁:讲清 “锁记录的创建→CAS 抢锁→归属权检查→回滚 / 成功” 的流程,体现对细节的理解。

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值