背景
JDK 6+ 对 synchronized 进行了深度优化(如偏向锁、轻量级锁),在无竞争/低竞争场景下性能甚至优于 CAS。
- 为什么?
CAS 需要多次重试(CPU 空转),而synchronized通过锁升级机制,避免了无效重试。
(类比:等红灯时,车不熄火(自旋)比熄火等待(阻塞)更高效,尤其在多核CPU上)
前置知识:对象头 & Mark Word
每个 Java 对象都有 对象头(内存中前 8-16 字节):
-
Mark Word:存储对象状态(哈希码、分代年龄等)。
加锁时,Mark Word 被替换为锁信息(如线程ID、锁记录指针):锁状态 Mark Word 内容 无锁 哈希码、分代年龄 偏向锁 线程ID、偏向时间戳 轻量级锁 指向锁记录(Lock Record)的指针 重量级锁 指向重量级锁(OS互斥量)的指针
💡 关键:锁升级是 单向的(无锁 → 偏向锁 → 轻量级锁 → 重量级锁),不可逆。
锁升级过程(核心!)
1. 偏向锁(Biased Locking)
-
适用场景:无竞争(只有单线程访问)。
-
优化点:避免每次重入都 CAS。
- 第一次加锁:用 CAS 将线程ID写入 Mark Word。
- 后续重入:直接检查 Mark Word 中的线程ID是否匹配 → 无需 CAS!
-
撤销条件:
- 有竞争 → 升级为轻量级锁(需 STW,暂停所有线程)。
- 访问
hashCode()→ 撤销偏向锁(因哈希码会覆盖 Mark Word)。
-
重偏向:对象被多个线程访问但无竞争时,可重偏向给新线程(批量操作,以类为单位)。
-
禁用:
-XX:-UseBiasedLocking(JVM 参数)。📌 比喻:课本占座(线程A占座后,自己回来直接用,不用问别人)。
2. 轻量级锁(Lightweight Locking)
-
适用场景:无竞争或少量竞争(线程交替执行,如 A 上课 → B 上课)。
-
机制:
- 每个线程栈帧中存 Lock Record(锁记录),包含对象 Mark Word 的副本。
- 加锁:用 CAS 将对象 Mark Word 指向 Lock Record。
- 成功:无竞争,继续执行。
- 失败:升级为重量级锁(锁膨胀)。
-
无竞争示例(线程A、B交替执行):
Object lock = new Object(); // 线程A:第一次加锁,CAS成功(Mark Word → Lock Record) synchronized (lock) { /* 代码 */ } // 无竞争时,直接执行 // 线程B:尝试加锁,CAS成功(Mark Word → 自己的Lock Record) synchronized (lock) { /* 代码 */ } // 无竞争,继续执行 -
锁膨胀示例(竞争发生 → 升级重量级锁):
Object lock = new Object(); // 线程A:成功加锁(轻量级锁) synchronized (lock) { // 线程B同时尝试加锁,CAS失败 → 触发锁膨胀 synchronized (lock) { /* 线程B升级为重量级锁 */ } }
📌 比喻:线程A占座(课本),回来发现课本没动 → 继续上课(无竞争);
有人动了课本 → 线程A立刻升级为铁栅栏(重量级锁)。
3. 重量级锁(Heavyweight Locking)
- 适用场景:高竞争(多个线程同时抢锁)。
- 优化:自旋锁(JDK 6+)
- 自旋:线程忙等(CPU 空转),等待锁释放(避免阻塞/唤醒开销)。
- 自适应:
- 之前自旋成功 → 多自旋几次(如 10 次);
- 之前自旋失败 → 少自旋甚至不自旋。
- 注意事项:
-
✅ 多核 CPU 有效(自旋占用 CPU,单核浪费)。
-
❌ 无法手动控制(JDK 7+ 无法关闭)。
📌 比喻:等红灯时,车不熄火(自旋)→ 等到绿灯直接走;
车熄火(阻塞)→ 等绿灯再启动(浪费时间)。
-
其他关键优化
| 优化方式 | 原理 | 示例 |
|---|---|---|
| 减少上锁时间 | 同步块内代码越短越好(避免 I/O、网络等耗时操作)。 | synchronized { shortOp(); } |
| 减少锁粒度 | 拆分大锁为多个小锁(提升并发度)。 | ConcurrentHashMap(分段锁) LongAdder(base + cells 数组,多线程并行累加) |
| 锁粗化 | 多次同步 → 合并为一次(JVM 自动优化)。 | for (i=0; i<10; i++) { sync(); } → sync() { for (i=0; i<10; i++) { } } |
| 锁消除 | JVM 逃逸分析:锁对象是局部变量(不被其他线程访问)→ 移除锁。 | void method() { Object lock = new Object(); sync(lock) { ... } } → 无锁 |
| 读写分离 | 读操作无锁,写操作加锁(写时复制)。 | CopyOnWriteArrayList(写时复制数组,读无锁) |
为什么优化这么重要?
-
无竞争:偏向锁 → 零 CAS 开销。
-
低竞争:轻量级锁 → CAS 成功,避免 OS 调用。
-
高竞争:自旋锁 → 用 CPU 换时间(比阻塞快)。
💡 总结:JVM 用“锁升级+自适应优化”动态匹配竞争场景,让 synchronized 在大多数场景下比 CAS 更高效。
📌 附:锁升级流程图
无锁 → 偏向锁(无竞争)→ 轻量级锁(少量竞争)→ 重量级锁(高竞争)
(箭头方向:单向,不可回退)
笔记原则:只保留核心逻辑,省略冗余代码。
建议:实际编码中,优先用 synchronized(JVM 优化好),少用 CAS(需手动处理重试)。