synchronized到底锁的是什么:对象头 Mark Word、Monitor、锁升级与排查

你在面试里被问 synchronized,真正的分水岭不是会不会背"它是互斥锁",而是能不能把这件事讲清楚:

  • synchronized 锁的到底是什么资源
  • synchronized 用在不同位置(实例方法 / 静态方法 / 代码块)锁对象分别是谁
  • JVM 里它怎么实现(对象头、Monitor)?
  • 为什么会有偏向锁/轻量锁/重量锁?锁升级怎么触发?
  • 线上卡顿、线程阻塞、死锁怎么定位?

这篇按"从能解释 -> 能写对 -> 能排查"的主线讲透。


1. 一句话结论:synchronized 锁的是"对象",不是"代码"

synchronized 的互斥,本质是:

  • 对某个锁对象(lock object)的 Monitor 进行进入/退出控制

所以你要先回答:

  • 这个 synchronized 对应的 锁对象到底是谁

2. 三种写法分别锁谁(最核心、也是最常考)

2.1 修饰实例方法:锁的是"当前实例 this"

java 复制代码
public class Counter {
    private int count = 0;

    public synchronized void inc() {
        count++;
    }
}

等价于:

java 复制代码
public void inc() {
    synchronized (this) {
        count++;
    }
}

结论:

  • 同一个对象实例 上的 inc() 会互斥
  • 不同对象实例互不影响

2.2 修饰静态方法:锁的是"类对象 Counter.class"

java 复制代码
public class Counter {
    private static int total = 0;

    public static synchronized void inc() {
        total++;
    }
}

等价于:

java 复制代码
public static void inc() {
    synchronized (Counter.class) {
        total++;
    }
}

结论:

  • 同一个类的所有线程都竞争同一把锁
  • 即使 new 多个实例,静态同步仍然互斥

2.3 同步代码块:锁的是你括号里传入的对象

java 复制代码
private final Object lock = new Object();

public void inc() {
    synchronized (lock) {
        // critical section
    }
}

结论:

  • 锁对象是谁,互斥范围就围绕谁
  • 锁对象必须是"共享且稳定的引用",否则锁等于没加

3. 写对的关键:锁对象必须"共享 + 不变"

3.1 典型错误:锁了一个会变的对象

java 复制代码
Integer lock = 1;

public void foo() {
    synchronized (lock) {
        lock++; // lock 引用变了
    }
}

问题:

  • 你以为锁住了 lock,但下一次进入 foo() 锁的可能是另一个对象

正确做法:

  • 使用 final Object lock = new Object()

3.2 典型错误:锁了 String 常量

java 复制代码
synchronized ("LOCK") {
    // ...
}

问题:

  • 字符串常量池会导致不同代码路径意外共享同一把锁

正确做法:

  • 使用私有的 final Object lock

4. JVM 里它怎么实现:对象头、Mark Word 与 Monitor

要讲得"像懂的人",你至少要能把这些关系说顺:

  • 每个对象都有对象头(Object Header)
  • 对象头里有 Mark Word(记录哈希、GC 信息、锁状态等)
  • 发生同步时,JVM 会基于对象头的锁状态,把对象与 Monitor(监视器) 关联起来

你不需要把每一位比特背出来,但需要知道:

  • synchronized 的锁状态会体现在对象头(Mark Word)里
  • 竞争加剧时会膨胀到 Monitor,线程会在 Monitor 上阻塞/唤醒

5. monitorenter/monitorexit:编译器帮你插入的指令

synchronized 在字节码层面是:

  • 进入临界区:monitorenter
  • 退出临界区:monitorexit

这也是为什么你经常听到:

  • "异常也会释放锁"

原因是:

  • 编译器会生成异常路径的 monitorexit,保证退出(前提是线程没有被强杀,正常异常退出会释放)

6. 可重入性:为什么同一线程可以反复进同一把锁

synchronized 是可重入的(Reentrant):

  • 同一线程持有锁后,再次进入同一锁对象的同步块,不会死锁

你可以这样解释:

  • Monitor 会记录"持有者线程 + 重入次数",同线程进入只递增计数

7. 锁升级:偏向锁 / 轻量锁 / 重量锁(工程上怎么理解)

面试里不用讲到源码,但建议你把"为什么要升级"讲清楚:

  • 无竞争:希望加锁几乎没有成本(偏向/轻量)
  • 有竞争:必须保证互斥与公平的唤醒阻塞(重量)

你可以用"成本模型"来描述:

  • 偏向锁:几乎不做 CAS 竞争(偏向某线程)
  • 轻量锁:少量线程竞争,用 CAS + 自旋
  • 重量锁:竞争激烈,自旋浪费 CPU,转为阻塞/唤醒

工程上你看到的现象通常是:

  • 自旋过多 -> CPU 飙高
  • 阻塞过多 -> 上下文切换多、RT 抖动

8. wait/notify 必考点:它们依赖 Monitor,且只能在同步块内调用

规则:

  • wait():释放锁并进入等待队列
  • notify()/notifyAll():唤醒等待线程(但被唤醒线程需要重新竞争锁)

常见坑:

  • 在没有持有该对象 Monitor 的情况下调用 wait/notify 会抛 IllegalMonitorStateException

9. 常见坑与最佳实践(非常实用)

  • 锁粒度尽量小:只包住共享变量的临界区,不要把 IO、RPC、长循环包进去
  • 避免锁顺序不一致:容易形成死锁
  • 避免在 synchronized 内调用外部系统:不可控延迟会扩大锁占用时间
  • 优先使用私有 final Object lock:锁对象可控,不被外部拿到

10. 线上怎么排查"谁在抢锁/谁持有锁"

10.1 先看线程状态

  • BLOCKED:在等待进入 Monitor(抢锁)
  • WAITING/TIMED_WAITING:可能在 wait() / park() / sleep

10.2 用 jstack 看"锁在谁手里"

你在 jstack 里重点看两类信息:

  • - waiting to lock <0x...> (a xxx):谁在等锁
  • - locked <0x...> (a xxx):谁持有锁

如果你看到多条线程围绕同一个 <0x...>,基本就是热点锁。

10.3 用 Arthas 快速定位(如果你线上允许)

  • thread -b:看阻塞线程
  • jad/trace:定位热点方法(按需)

11. 面试表达(30 秒讲清楚)

  • synchronized 锁的是对象的 Monitor。
  • 修饰实例方法锁 this,修饰静态方法锁 Class 对象,同步代码块锁括号里的对象。
  • JVM 通过对象头 Mark Word 记录锁状态,竞争加剧会膨胀到 Monitor,线程在 Monitor 上阻塞/唤醒。
  • 它是可重入的;wait/notify 必须在持有同一对象锁的同步块内调用。
  • 排查锁竞争:看线程 BLOCKED,用 jstack 找谁 locked/谁 waiting to lock

12. 总结

  • synchronized 的关键不是"写上关键字",而是锁对象要选对
  • 想讲得高级:讲清楚对象头/Monitor/锁升级的成本模型
  • 想排查得快:用线程状态 + jstack 抓锁持有者与等待者
相关推荐
小强库计算机毕业设计42 分钟前
SpringBoot+Vue3 学生宿舍管理系统实战
java·spring boot·后端·vue·学生宿舍管理系统
李冰然1 小时前
深入Java集合框架:HashMap源码解析(JDK 8)
java
人道领域2 小时前
【0-1的agent进阶篇】RAG:从Embedding到检索增强生成的底层逻辑
java·embedding·agent·rag
桦说编程2 小时前
盘点并发集合里那些容易误判的行为
java·后端·性能优化
IT爱学堂3 小时前
java版数据结构和算法+AI算法和技能学习指南
java·开发语言
evans在进步4 小时前
LeetCode 394:字符串解码——Java 单栈模拟与嵌套解析详解
java·python·leetcode
AI人工智能+电脑小能手4 小时前
大白话说Java设计模式-17-装饰器模式(源码剖析篇)
java·spring·设计模式·装饰器模式·源码分析·io流
m0_547486665 小时前
《Java程序设计与实践 》全套PPT课件2026
java·开发语言
土司大王5 小时前
LeetCode hto100——字母异位词分组
java·算法·leetcode
码匠许师傅5 小时前
【C++ 面试真题】聊聊 C++ 中的 lambda 表达式
java·c++·面试