Skip to content

概念卡片:synchronized 锁升级与 Monitor

一句话机制

synchronized 的锁存在对象头 Mark Word 里,靠 Monitor(底层是 OS 的 Mutex Lock)实现互斥;JDK6 为减少加解锁开销引入「偏向锁→轻量级锁→重量级锁」四级升级,锁状态只升不降。 理解锁升级 = 理解"锁是拿 CPU 自旋换线程阻塞"的权衡过程。

两个前置概念

  • Java 对象头 = Mark Word(存 hashCode/分代年龄/锁标志位/偏向线程 ID,随锁状态动态复用)+ Klass Pointer(类型指针,指向类元数据)
  • Monitor:每个 Java 对象都有一把看不见的锁(内部锁/Monitor 锁),Monitor 里 Owner 字段记录持锁线程;依赖 OS 的 Mutex Lock 实现

锁升级四级(只升不降)

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
状态标志位触发条件加锁方式代价
无锁01无竞争CAS(乐观锁)几乎无
偏向锁01+偏向位同线程反复加锁仅 1 次 CAS 换 ThreadID极低
轻量级锁00有竞争但轻微CAS + 自旋(不阻塞)占 CPU
重量级锁10自旋超限/多线程竞争OS Mutex,线程阻塞上下文切换

各状态要点

  • 偏向锁:一段同步代码总被同一线程访问时,Mark Word 里存锁偏向的线程 ID,进出同步块只检测 ID、不再 CAS。撤销偏向锁要等全局安全点(暂停持锁线程),撤销后回到无锁或轻量级锁。
  • 轻量级锁:偏向锁被其他线程访问时升级。线程在栈帧里建 Lock Record 拷贝 Mark Word,再用 CAS 把对象头 Mark Word 换成指向 Lock Record 的指针。CAS 失败则判断「是否自己已持锁」(重入)还是「多线程竞争」(升级重量级)。
  • 重量级锁:自旋超过一定次数,或「一持锁 + 一自旋 + 第三方来访」时升级,等待线程全部阻塞。

为什么不直接都用重量级锁:阻塞/唤醒线程要 OS 切换 CPU 状态、耗 CPU 时间,若同步块内容极简单,状态切换耗时可能比用户代码还长——这就是 JDK6 之前 synchronized 慢的根因,也是引入偏向锁/轻量级锁的动机。

锁的其它分类(15 种锁速览)

维度分类一句话
是否加锁悲观锁 / 乐观锁先加锁 vs 更新时检查(CAS)
是否自旋自旋锁 / 适应性自旋锁失败后循环重试 vs 自适应次数
公平性公平锁 / 非公平锁按申请顺序 vs 可插队(吞吐高但可能饥饿)
可重入可重入锁 / 非重入锁ReentrantLock 靠 state 计数支持重入
独占性共享锁 / 排他锁读锁可并发 vs 写锁独占
粒度分段锁ConcurrentHashMap 按段加锁降低竞争

公平锁 vs 非公平锁

  • 公平锁:按申请顺序进队列,队首才获锁 → 不饥饿,但吞吐低(除队首外全阻塞、唤醒开销大)
  • 非公平锁:新线程直接尝试抢锁,抢不到才排队 → 吞吐高(有机会不阻塞),但队列线程可能饥饿

常见误解(避坑)

  1. ❌ "锁状态可以降级"。→ synchronized 锁只能升级不能降级(偏向锁撤销会回到无锁或轻量级,但整体路径是单向升级的)。
  2. ❌ "synchronized 底层直接就是重量级锁"。→ JDK6 后有偏向锁/轻量级锁两级缓冲,多数单线程/轻竞争场景根本到不了重量级。
  3. ❌ "轻量级锁就一定比重量级快"。→ 轻量级锁靠自旋,竞争激烈时大量线程空转占 CPU,反而不如直接阻塞(所以超限后升级)。
  4. ❌ "ReentrantLock 和 synchronized 性能天差地别"。→ JDK6 锁优化后两者性能接近;差异在功能(可中断、可超时、可条件变量、公平锁),不在绝对速度。

关联

最近更新