首页 > 项目 > 当前页面

Java锁升级机制详解:偏向锁、轻量级锁、重量级锁是如何一步步升级的?

2026-06-10 NEW个对象

🔥 Java锁升级机制详解:偏向锁、轻量级锁、重量级锁是如何一步步升级的?

📌 核心结论:
Java中的synchronized并不是一开始就使用重量级锁。为了减少线程竞争带来的性能损耗,HotSpot JVM设计了锁升级机制。锁会根据竞争情况经历无锁 → 偏向锁 → 轻量级锁 → 重量级锁的升级过程。锁只能升级不能降级,从而保证线程安全并提高系统吞吐量。

1️⃣ 问题背景

在JDK1.5之前,synchronized一直被认为是重量级锁。

每次线程进入同步代码块都需要向操作系统申请Mutex互斥量。

线程阻塞和唤醒涉及用户态和内核态切换。

这种切换代价非常高。

例如:

synchronized(lock){
  //业务逻辑
}

即使只有一个线程执行,也会触发重量级锁逻辑。

显然存在大量性能浪费。

⚠️ 大部分业务场景实际上并不存在激烈竞争,因此JVM引入了锁升级机制。

从JDK1.6开始,HotSpot对synchronized进行了大量优化。

  • 偏向锁
  • 轻量级锁
  • 自旋锁
  • 锁消除
  • 锁粗化

其中最核心的就是锁升级机制。

2️⃣ 核心原理

锁升级本质上是根据竞争程度动态调整锁状态。

竞争越小,锁越轻。

竞争越大,锁越重。

无锁
↓
偏向锁
↓
轻量级锁
↓
重量级锁

升级过程依赖对象头中的Mark Word实现。

Mark Word记录了对象运行时状态。

  • HashCode
  • GC年龄
  • 锁标志位
  • 线程ID
  • 偏向锁状态

JVM通过修改Mark Word中的标志位完成锁状态切换。

💡 面试核心回答:锁升级本质是Mark Word不断变化的过程。

3️⃣ 数据结构分析

对象头结构

HotSpot对象主要由对象头和实例数据组成。

Object
├── Mark Word
└── Klass Pointer

锁状态信息主要保存在Mark Word中。

无锁状态

Mark Word
↓
HashCode
GC Age
Lock Flag

对象刚创建时处于无锁状态。

此时没有线程持有锁。

偏向锁状态

偏向锁会记录线程ID。

Mark Word
↓
ThreadId
Epoch
BiasedFlag

如果后续还是同一个线程访问同步块。

无需执行CAS竞争。

轻量级锁状态

轻量级锁会创建Lock Record。

线程栈
↓
Lock Record
↓
CAS替换Mark Word

多个线程竞争时进入轻量级锁阶段。

重量级锁状态

竞争进一步加剧。

JVM创建Monitor对象。

Object
↓
Monitor
↓
EntryList
↓
WaitSet

未获得锁的线程进入阻塞状态。

4️⃣ 算法分析

偏向锁算法

偏向锁假设大部分时间只有一个线程访问同步块。

第一次获取锁时:

MarkWord.ThreadId = 当前线程ID

后续进入同步块:

if(ThreadId == 当前线程){
  直接进入
}

无需CAS。

无需加锁。

效率最高。

轻量级锁算法

当第二个线程尝试获取锁时。

偏向锁撤销。

升级轻量级锁。

CAS(MarkWord,LockRecord)

CAS成功:

获得锁。

CAS失败:

进入自旋阶段。

自旋锁算法

线程不会立即阻塞。

而是在CPU中循环尝试获取锁。

while(lock != null){
  retry();
}

如果短时间内锁被释放。

即可直接获得锁。

避免线程切换开销。

重量级锁算法

自旋次数达到阈值。

或者竞争线程持续增多。

锁膨胀为重量级锁。

park(thread)
unpark(thread)

线程进入阻塞队列。

等待唤醒。

5️⃣ 执行流程

对象创建
↓
无锁状态
↓
线程A进入同步块
↓
升级偏向锁
↓
线程A再次进入
↓
直接执行
↓
线程B开始竞争
↓
撤销偏向锁
↓
升级轻量级锁
↓
CAS竞争
↓
竞争激烈
↓
自旋重试
↓
持续失败
↓
升级重量级锁
↓
线程阻塞等待

6️⃣ 实际案例

案例一:单线程场景

public void test(){
  synchronized(this){
    //业务代码
  }
}

只有一个线程访问。

锁升级为偏向锁。

后续进入同步块无需竞争。

案例二:两个线程竞争

ThreadA
ThreadB

ThreadA已经持有偏向锁。

ThreadB到来后。

偏向锁撤销。

升级轻量级锁。

双方通过CAS竞争。

案例三:高并发竞争

100个线程
同时访问同步块

CAS大量失败。

自旋消耗CPU严重。

JVM自动膨胀为重量级锁。

线程进入Monitor等待队列。

❌ 重量级锁涉及内核态切换,性能损耗最大。

7️⃣ 优缺点分析

锁状态 优点 缺点
偏向锁 几乎无开销 多线程竞争时需撤销
轻量级锁 避免线程阻塞 CAS失败消耗CPU
自旋锁 减少上下文切换 长时间竞争浪费CPU
重量级锁 保证公平竞争 性能最差

8️⃣ 面试常见问题

synchronized底层是什么实现?

依赖对象头Mark Word和Monitor对象实现。

锁升级过程是什么?

无锁 → 偏向锁 → 轻量级锁 → 重量级锁。

锁为什么只能升级不能降级?

避免频繁转换带来的性能损耗和状态复杂度。

偏向锁为什么快?

只记录线程ID,不需要CAS操作。

轻量级锁为什么使用CAS?

利用乐观锁思想减少线程阻塞。

什么时候会升级重量级锁?

CAS竞争失败次数过多或者线程竞争激烈时。

JDK15以后还有偏向锁吗?

JDK15默认关闭偏向锁。

JDK18开始已经彻底移除偏向锁实现。

Monitor是什么?

Monitor是HotSpot实现重量级锁的核心对象,内部维护Owner、EntryList和WaitSet等结构。

9️⃣ 总结

✅ synchronized并非一开始就是重量级锁。

✅ JVM通过锁升级机制实现性能优化。

✅ 锁升级过程为无锁、偏向锁、轻量级锁、重量级锁。

✅ 偏向锁适合单线程访问场景。

✅ 轻量级锁依赖CAS竞争实现。

✅ 自旋锁通过忙等待减少线程切换。

✅ 竞争激烈时最终升级为重量级锁。

✅ 锁升级过程本质上是Mark Word状态不断变化的过程。

🎯 面试一句话总结:

Java锁升级机制是JVM对synchronized的重要优化,锁会根据竞争情况从无锁升级为偏向锁、轻量级锁以及重量级锁。其中偏向锁利用线程ID避免CAS,轻量级锁利用CAS避免阻塞,竞争激烈时升级为Monitor重量级锁,从而在保证线程安全的同时最大程度提升并发性能。

相关文章

NEW个对象 NEW个对象
JAVA是世界上最好的语言