当前位置: 首页 > news >正文

synchronized锁优化与升级机制

synchronized锁优化与升级机制

深入理解JVM对synchronized的优化:从无锁到偏向锁、轻量级锁,再到重量级锁的完整升级过程。

为什么需要锁优化

传统synchronized的问题

在JDK 1.6之前,synchronized是重量级锁:

传统synchronized的缺点:
├─ 总是使用Monitor(重量级锁)
├─ 涉及用户态↔内核态切换
├─ 需要操作系统调度
└─ 性能开销大

性能对比(JDK 1.5):

// 测试代码
int count = 0;// 无锁
for (int i = 0; i < 1000000; i++) {count++;
}
// 耗时:~2ms// synchronized(总是重量级)
synchronized (lock) {for (int i = 0; i < 1000000; i++) {count++;}
}
// 耗时:~200ms(慢100倍!)

JDK 1.6的优化

JDK 1.6对synchronized进行了大量优化,引入了锁升级机制:

锁升级路径:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁优化效果:
├─ 大部分情况下不需要重量级锁
├─ 性能提升10-100倍
└─ synchronized变得高效

优化后的性能(JDK 1.8+):

// synchronized(有锁优化)
synchronized (lock) {for (int i = 0; i < 1000000; i++) {count++;}
}
// 耗时:~3-5ms(接近无锁!)

锁的四种状态

状态概览

Java对象头的Mark Word存储锁状态信息:

┌─────────────────────────────────────┐
│        锁状态(64位JVM)             │
├─────────────────────────────────────┤
│ 无锁(Normal)                       │
│ ├─ 锁标志位:01                      │
│ ├─ 偏向锁标志:0                     │
│ └─ 存储:hashCode、GC年龄            │
├─────────────────────────────────────┤
│ 偏向锁(Biased)                     │
│ ├─ 锁标志位:01                      │
│ ├─ 偏向锁标志:1                     │
│ └─ 存储:线程ID、epoch、GC年龄       │
├─────────────────────────────────────┤
│ 轻量级锁(Lightweight Locked)       │
│ ├─ 锁标志位:00                      │
│ └─ 存储:指向栈中Lock Record的指针   │
├─────────────────────────────────────┤
│ 重量级锁(Heavyweight Locked)       │
│ ├─ 锁标志位:10                      │
│ └─ 存储:指向Monitor的指针           │
└─────────────────────────────────────┘

Mark Word结构详解

64位JVM的Mark Word
无锁状态:
┌─────────┬──────────┬────┬────┬───┬──┐
│unused(25)│hashCode(31)│un-│age │0│01│
│          │            │used│(4) │ │  │
└─────────┴──────────┴────┴────┴───┴──┘↑identity hashCode偏向锁状态:
┌───────────┬────────┬────┬────┬───┬──┐
│Thread ID  │epoch(2)│un- │age │1│01│
│   (54)    │        │used│(4) │ │  │
└───────────┴────────┴────┴────┴───┴──┘↑偏向的线程ID轻量级锁:
┌──────────────────────────────────┬──┐
│指向栈中Lock Record的指针(62bit)    │00│
└──────────────────────────────────┴──┘重量级锁:
┌──────────────────────────────────┬──┐
│指向Monitor对象的指针(62bit)        │10│
└──────────────────────────────────┴──┘GC标记:
┌──────────────────────────────────┬──┐
│        空                         │11│
└──────────────────────────────────┴──┘

锁状态对比

锁状态优点缺点适用场景
无锁性能最好无同步保证无竞争
偏向锁加锁解锁无需CAS有竞争需要撤销一个线程访问同步块
轻量级锁竞争不激烈时性能好自旋消耗CPU线程交替执行同步块
重量级锁不消耗CPU线程阻塞,上下文切换竞争激烈

偏向锁详解

基本概念

偏向锁(Biased Locking):锁偏向于第一个获取它的线程。

核心思想:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。

工作原理

偏向锁的获取
线程第一次进入同步块:步骤1:检查Mark Word├─ 如果是无锁状态(biased_lock=0)└─ 进入步骤2步骤2:使用CAS将Mark Word替换为偏向锁├─ Thread ID = 当前线程ID├─ biased_lock = 1├─ lock = 01└─ CAS成功 → 获得偏向锁步骤3:以后该线程再进入├─ 检查Thread ID == 当前线程├─ 如果相等 → 直接进入(无需CAS!)└─ 如果不相等 → 偏向锁撤销

图示

初始状态(无锁):
Object Header:
┌─────────────────────────────┐
│ hashCode | age | 0 | 01     │
└─────────────────────────────┘Thread-1第一次加锁:
┌─────────────────────────────┐
│ Thread-1 ID | age | 1 | 01  │ ← CAS设置
└─────────────────────────────┘Thread-1再次加锁:
┌─────────────────────────────┐
│ Thread-1 ID | age | 1 | 01  │ ← 直接进入,无CAS!
└─────────────────────────────┘
代码示例
public class BiasedLockDemo {private static Object obj = new Object();public static void main(String[] args) throws InterruptedException {// JVM启动后需要等待4秒才会启用偏向锁Thread.sleep(5000);Thread t1 = new Thread(() -> {synchronized (obj) {System.out.println("第一次加锁");// 第一次:CAS设置偏向锁}synchronized (obj) {System.out.println("第二次加锁");// 第二次:检查Thread ID,直接进入}synchronized (obj) {System.out.println("第三次加锁");// 第三次:检查Thread ID,直接进入}});t1.start();t1.join();// 使用JOL查看对象头System.out.println(ClassLayout.parseInstance(obj).toPrintable());}
}

对象头变化

第一次加锁前:
00000000 00000000 00000000 00000000 ... 00000001  (无锁 01)第一次加锁后:
00000000 00000000 00000111 10110101 ... 00000101  (偏向锁 101)↑Thread ID

偏向锁的撤销

当其他线程尝试获取偏向锁时,需要撤销偏向锁:

偏向锁撤销流程:1. 暂停拥有偏向锁的线程(STW)└─ 到达安全点2. 检查持有偏向锁的线程状态├─ 如果线程不再活跃 → 直接撤销└─ 如果线程还活跃 → 继续步骤33. 检查线程是否还在同步块中├─ 如果不在 → 撤销偏向锁,改为无锁└─ 如果在 → 升级为轻量级锁4. 恢复线程执行

示例

public class BiasedLockRevoke {private static Object obj = new Object();public static void main(String[] args) throws InterruptedException {Thread.sleep(5000);// Thread-1持有偏向锁Thread t1 = new Thread(() -> {synchronized (obj) {System.out.println("t1: 获得偏向锁");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();Thread.sleep(100);// Thread-2尝试获取锁 → 偏向锁撤销Thread t2 = new Thread(() -> {synchronized (obj) {System.out.println("t2: 偏向锁被撤销,升级为轻量级锁");}});t2.start();}
}

批量重偏向和批量撤销

当撤销次数达到阈值时,JVM会进行批量操作:

批量重偏向(Bulk Rebias):
├─ 触发条件:同一类型对象撤销次数达到20次
├─ 操作:epoch++,使旧的偏向失效
└─ 效果:新的偏向可以快速建立批量撤销(Bulk Revoke):
├─ 触发条件:同一类型对象撤销次数达到40次
├─ 操作:关闭该类的偏向锁
└─ 效果:该类的对象直接使用轻量级锁

偏向锁的关闭

# 关闭偏向锁
-XX:-UseBiasedLocking# 延迟启用偏向锁(默认4秒)
-XX:BiasedLockingStartupDelay=0# 查看偏向锁统计
-XX:+PrintBiasedLockingStatistics

轻量级锁详解

基本概念

轻量级锁(Lightweight Locking):通过CAS操作避免使用重量级锁的互斥量。

核心思想:在没有多线程竞争的情况下,通过CAS减少重量级锁的使用。

工作原理

加锁过程
线程尝试获取轻量级锁:步骤1:在栈帧中创建Lock Record├─ 拷贝对象头的Mark Word到Lock Record└─ Lock Record中有一个owner指针指向锁对象步骤2:使用CAS尝试将对象头的Mark Word替换为指向Lock Record的指针├─ CAS成功 → 获得锁└─ CAS失败 → 步骤3步骤3:检查是否是重入├─ 如果Mark Word指向当前线程的栈帧│   → 重入,添加一个Lock Record(Displaced Mark Word为null)└─ 否则 → 锁竞争,自旋步骤4:自旋一定次数后└─ 升级为重量级锁

图示

加锁前:
Object:                      Thread Stack:
┌─────────────┐
│ Mark Word   │
│ hashCode|01 │
└─────────────┘加锁中:
Object:                      Thread Stack:
┌─────────────┐              ┌──────────────────┐
│ Mark Word   │              │ Lock Record      │
│  ─────────────────────────>│ ├─ Displaced MW  │
│ ptr to LR|00│              │ │   (hashCode)   │
└─────────────┘              │ └─ owner → Object│└──────────────────┘重入:
Object:                      Thread Stack:
┌─────────────┐              ┌──────────────────┐
│ Mark Word   │              │ Lock Record (2)  │
│  ─────────────────────────>│ ├─ Displaced MW  │
│ ptr to LR|00│              │ │   (null)       │← 重入标记
└─────────────┘              │ └─ owner → Object│├──────────────────┤│ Lock Record (1)  ││ ├─ Displaced MW  ││ │   (hashCode)   ││ └─ owner → Object│└──────────────────┘
解锁过程
解锁过程:步骤1:取出Lock Record中的Displaced Mark Word步骤2:使用CAS将Displaced Mark Word替换回对象头├─ CAS成功 → 解锁成功└─ CAS失败 → 说明有竞争,锁已升级为重量级锁步骤3:如果是重量级锁└─ 按照重量级锁的方式释放(释放Monitor)
代码示例
public class LightweightLockDemo {private static Object obj = new Object();public static void main(String[] args) throws InterruptedException {// 关闭偏向锁,直接使用轻量级锁// -XX:-UseBiasedLockingThread t1 = new Thread(() -> {synchronized (obj) {System.out.println("t1: 获得轻量级锁");// CAS设置Lock Record指针}});t1.start();t1.join();// 使用JOL查看对象头System.out.println(ClassLayout.parseInstance(obj).toPrintable());}
}

自旋优化

轻量级锁使用**自旋(Spinning)**来避免线程阻塞:

// 自旋等待锁
int spinCount = 0;
while (!tryAcquireLock()) {spinCount++;if (spinCount > MAX_SPIN_COUNT) {// 自旋次数过多,升级为重量级锁inflateToHeavyweight();break;}// 短暂等待(自旋)// CPU会一直执行这个循环
}

自旋的优缺点

优点:
├─ 避免线程阻塞和唤醒
├─ 减少上下文切换
└─ 适合锁持有时间短的场景缺点:
├─ 消耗CPU资源
├─ 如果锁持有时间长,浪费CPU
└─ 自旋次数难以确定

自适应自旋

JVM会根据历史记录动态调整自旋次数:

自适应策略:
├─ 如果上次自旋成功 → 增加自旋次数
├─ 如果上次自旋失败 → 减少自旋次数
└─ 如果某个锁自旋很少成功 → 直接跳过自旋

重量级锁详解

基本概念

重量级锁(Heavyweight Lock):使用操作系统的互斥量(Mutex)实现的锁。

特点

  • 🔒 使用Monitor对象
  • 🔒 线程会被阻塞(BLOCKED状态)
  • 🔒 涉及用户态↔内核态切换
  • 🔒 性能开销最大

升级时机

升级为重量级锁的条件:1. 轻量级锁自旋失败└─ 自旋次数超过阈值2. 轻量级锁CAS失败└─ 有线程正在持有锁3. wait/notify调用└─ 这些操作需要Monitor

锁膨胀过程

锁膨胀(Lock Inflation):步骤1:创建Monitor对象步骤2:将Mark Word设置为指向Monitor的指针└─ 锁标志位改为10步骤3:将当前持有轻量级锁的线程设置为Monitor的Owner步骤4:竞争失败的线程进入Monitor的EntryList步骤5:后续操作按照Monitor机制进行

图示

轻量级锁:
Object:                   Thread Stack:
┌──────────────┐          ┌─────────────┐
│ ptr to LR|00 │────────> │ Lock Record │
└──────────────┘          └─────────────┘膨胀为重量级锁:
Object:                   Monitor:
┌──────────────┐          ┌─────────────────┐
│ ptr to Mon|10│────────> │ Owner: Thread-1 │
└──────────────┘          │ EntryList:      ││  ├─ Thread-2    ││  └─ Thread-3    ││ WaitSet: []     │└─────────────────┘

锁升级过程

完整升级路径

┌──────────────────────────────────────────────────────────────┐
│              synchronized锁升级完整流程                       │
└──────────────────────────────────────────────────────────────┘┌─────────────┐│   无锁状态   ││MarkWord:    ││ hashCode等  │└──────┬──────┘│第一个线程synchronized(obj)│▼┌────────────────────┐│ JVM检查是否可偏向?│└──┬──────────┬──────┘YES       NO│         │▼         └───────────────┐┌─────────────┐                  ││  偏向锁状态  │                  ││MarkWord:    │                  ││ Thread ID   │                  │└──────┬──────┘                  ││                         │其他线程synchronized(obj)            ││                         │▼                         │┌───────────────────────┐             ││ 偏向线程还在执行同步块?│             │└──┬───────────┬────────┘             │YES         NO                      ││          │                       ││          ▼                       ││   ┌──────────────┐               ││   │ 偏向锁撤销    │               ││   │(到安全点)   │               ││   └──────┬───────┘               ││          │                       ││          └───────────┬───────────┘▼                      ▼┌─────────┐         ┌──────────────┐│ 升级为  │         │  升级为       ││重量级锁 │         │ 轻量级锁      │└─────────┘         │ MarkWord:     ││ Lock Record   ││ 指针          │└──────┬────────┘│多线程竞争,CAS自旋│▼┌────────────────────┐│ CAS自旋获取锁成功? │└──┬──────────┬──────┘YES        NO│          │▼          ▼┌────────┐  ┌─────────────┐│轻量级锁│  │ 自旋一定次数  ││运行     │  │ 仍失败?      │└────────┘  └──┬──────────┘│ YES▼┌─────────────────┐│ 膨胀为重量级锁   ││ MarkWord:       ││ Monitor指针     │└─────────────────┘【关键点】:
✅ 锁只能升级,不能降级(JDK 15之前)
✅ 偏向锁撤销需要到达安全点(STW)
✅ 轻量级锁使用CAS+自旋
✅ 重量级锁使用操作系统互斥量

各阶段Mark Word变化

阶段1:无锁 → 偏向锁
┌────────────────────────────┐      ┌────────────────────────────┐
│ hashCode | age | 0 | 01    │  →  │ ThreadID | epoch|age|1|01  │
└────────────────────────────┘      └────────────────────────────┘hashCode被覆盖                      记录偏向线程ID阶段2:偏向锁 → 轻量级锁
┌────────────────────────────┐      ┌────────────────────────────┐
│ ThreadID | epoch|age|1|01  │  →  │ Lock Record指针    | 00    │
└────────────────────────────┘      └────────────────────────────┘撤销偏向,CAS替换                    指向线程栈的Lock Record阶段3:轻量级锁 → 重量级锁
┌────────────────────────────┐      ┌────────────────────────────┐
│ Lock Record指针    | 00    │  →  │ Monitor指针        | 10    │
└────────────────────────────┘      └────────────────────────────┘自旋失败,膨胀                      指向Monitor对象

详细示例

import org.openjdk.jol.info.ClassLayout;public class LockUpgradeDemo {public static void main(String[] args) throws InterruptedException {Object obj = new Object();// 等待偏向锁启用Thread.sleep(5000);System.out.println("===== 初始状态(无锁) =====");System.out.println(ClassLayout.parseInstance(obj).toPrintable());// 阶段1:偏向锁Thread t1 = new Thread(() -> {synchronized (obj) {System.out.println("\n===== t1获得偏向锁 =====");System.out.println(ClassLayout.parseInstance(obj).toPrintable());}});t1.start();t1.join();System.out.println("\n===== t1释放后(仍是偏向锁) =====");System.out.println(ClassLayout.parseInstance(obj).toPrintable());// 阶段2:轻量级锁Thread t2 = new Thread(() -> {synchronized (obj) {System.out.println("\n===== t2获得锁(轻量级) =====");System.out.println(ClassLayout.parseInstance(obj).toPrintable());}});t2.start();t2.join();// 阶段3:重量级锁Thread t3 = new Thread(() -> {synchronized (obj) {System.out.println("\n===== t3持有锁(重量级) =====");System.out.println(ClassLayout.parseInstance(obj).toPrintable());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}});Thread t4 = new Thread(() -> {synchronized (obj) {System.out.println("\n===== t4获得锁 =====");}});t3.start();Thread.sleep(100);t4.start();Thread.sleep(500);System.out.println("\n===== 竞争中(重量级锁) =====");System.out.println(ClassLayout.parseInstance(obj).toPrintable());t3.join();t4.join();}
}

输出分析

===== 初始状态(无锁) =====
...00000001  (锁标志位 01,无偏向)===== t1获得偏向锁 =====
...00000101  (锁标志位 01,有偏向)
Thread ID: 0x00007f8a1c001000===== t1释放后(仍是偏向锁) =====
...00000101  (偏向锁不会主动撤销)
Thread ID: 0x00007f8a1c001000===== t2获得锁(轻量级) =====
...00000000  (锁标志位 00,轻量级锁)
Lock Record Pointer: 0x00007f8a1c123456===== 竞争中(重量级锁) =====
...00000010  (锁标志位 10,重量级锁)
Monitor Pointer: 0x00007f8a1c789abc

锁降级

注意:Java的锁通常不会降级!

锁升级:✅ 支持
无锁 → 偏向锁 → 轻量级锁 → 重量级锁锁降级:❌ 通常不支持
重量级锁 → 轻量级锁 → 偏向锁 → 无锁

原因

  • 降级实现复杂
  • 降级判断开销大
  • 大部分场景不需要降级

特殊情况

  • GC时可能清理无用的Monitor
  • 偏向锁epoch机制可以理解为一种"降级"

锁优化技术

1. 锁消除(Lock Elimination)

JIT编译器会消除不可能存在竞争的锁:

public void method() {// 局部变量,不可能被其他线程访问Object lock = new Object();synchronized (lock) {  // ← 这个锁会被消除// ...}
}// JIT优化后等价于:
public void method() {// 直接执行,无锁// ...
}

触发条件

  • 逃逸分析证明对象不会逃逸
  • 锁对象只在当前线程可见

2. 锁粗化(Lock Coarsening)

将多个连续的加锁操作合并为一个:

// 优化前:频繁加锁解锁
for (int i = 0; i < 1000; i++) {synchronized (lock) {list.add(i);}
}// JIT优化后:锁粗化
synchronized (lock) {for (int i = 0; i < 1000; i++) {list.add(i);}
}

3. 自适应自旋

动态调整自旋次数:

历史记录:
├─ 上次自旋成功 → 增加自旋次数(最多10次)
├─ 上次自旋失败 → 减少自旋次数
└─ 连续失败 → 跳过自旋,直接阻塞

4. 偏向锁优化

优化策略:
├─ 延迟启用(默认4秒后)
├─ 批量重偏向(epoch机制)
└─ 批量撤销(类级别关闭)

🎯 知识点总结

锁状态对比

锁状态Mark Word获取方式释放方式适用场景
无锁hashCode|01--无竞争
偏向锁Thread ID|101CAS一次撤销时处理单线程反复进入
轻量级锁Lock Record|00CASCAS交替执行
重量级锁Monitor|10MonitorMonitor竞争激烈

锁升级条件

无锁 → 偏向锁:
└─ 第一个线程进入同步块偏向锁 → 轻量级锁:
└─ 其他线程尝试获取锁轻量级锁 → 重量级锁:
├─ 自旋次数超过阈值
├─ 自旋线程数超过CPU核心数的一半
└─ 调用wait/notify

优化建议

  1. 减少锁的持有时间
  2. 减小锁的粒度
  3. 使用读写锁代替独占锁
  4. 锁分离(如ReadWriteLock)
  5. 锁分段(如ConcurrentHashMap)

💡 常见面试题

Q1:synchronized的锁有哪几种状态?

:4种状态:无锁、偏向锁、轻量级锁、重量级锁。随着竞争激烈程度,锁会从无锁升级到偏向锁,再到轻量级锁,最后到重量级锁。这个过程是单向的,通常不会降级。

Q2:什么是偏向锁?

:偏向锁是一种针对加锁操作的优化手段。假设某个锁大部分时间都是被同一个线程获取,那么这个锁就"偏向"这个线程。偏向锁使用CAS一次性将线程ID记录在对象头中,之后该线程再次进入时只需检查线程ID,无需CAS,大大提高了性能。

Q3:轻量级锁是如何工作的?

:轻量级锁通过CAS操作避免使用互斥量。加锁时,在栈帧中创建Lock Record,使用CAS将对象头替换为指向Lock Record的指针。如果CAS失败,会进行自旋等待。自旋一定次数后仍失败,则升级为重量级锁。

Q4:为什么需要自旋?

:因为线程阻塞和唤醒需要从用户态切换到内核态,开销很大。如果锁很快就会被释放,那么自旋等待比阻塞更高效。自旋适合锁持有时间短、竞争不激烈的场景。

Q5:synchronized在JDK 1.6前后有什么变化?

:JDK 1.6之前,synchronized总是使用重量级锁,性能较差。JDK 1.6引入了偏向锁、轻量级锁、自旋锁、锁消除、锁粗化等优化,使得synchronized在大部分情况下性能接近甚至超过ReentrantLock,成为高效的同步机制。

http://www.dtcms.com/a/528669.html

相关文章:

  • 设计公司网站运营wordpress+编辑模板
  • URL下载网络资源
  • Spring Bean注解终极指南:从入门到精通
  • wordpress旅游类网站深圳哪里做网站
  • 【FPGA】38译码器板级验证
  • 初学JVM---什么是JVM
  • 企培内训APP开发案例:实现视频课程、考试与绩效考核一体化
  • 网站后台怎么上传图片产品wordpress不能搜索文章
  • 网站首页默认的文件名一般为云指官网
  • Kafka消费者在金融领域的深度实践:从交易处理到风险控制的完整架构
  • 使用阿里云效搭建个人maven私有仓库
  • Android Studio新手开发第三十一天
  • (四)Gradle 依赖树分析与依赖关系优化
  • Drogon: 一个开源的C++高性能Web框架
  • Java Stream 流:让数据处理更优雅的 “魔法管道“
  • 查看网站服务器ip受欢迎的购物网站建设
  • fpga实现音频预加重(pre-emphasis)滤波器
  • Rust中的Enum与Struct详解
  • C语言进阶知识--自定义类型:结构体
  • OptionMaster Pro:期权数据智能处理系统的设计与实现
  • C. Maximum GCD on Whiteboard
  • 【AI论文】DITING:网络小说翻译评估的多智能体基准测试框架
  • 吉林省软环境建设网站免费开网站系统
  • vulnerable_docker_containement 靶机
  • Docker方式安装Nginx
  • 标签噪声学习:理论与方法详解
  • Docker 部署 Debian 全流程教程
  • 上海做网站公司wordpress 活动网站
  • Bee:从 0 到 1 打造一套现代化的全栈后台管理系统(React + Spring Boot)
  • 计算机操作系统:“抖动”与工作集