抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

JDK 6之前,synchronized是重量级锁,性能较差。JDK 6引入了偏向锁、轻量级锁等优化机制,大幅提升了synchronized的性能。本文深入剖析JVM对synchronized的各种优化策略和锁升级的完整过程。

为什么需要锁优化

重量级锁的问题

JDK 6之前,synchronized直接使用操作系统的互斥量(Mutex Lock)实现:

1
2
3
4
5
线程获取锁失败 → 阻塞 → 操作系统介入 → 用户态切换到内核态

用户态 ←──────────────────────────────▶ 内核态
上下文切换
(数千个CPU周期)

问题:

  • 每次加锁/解锁都需要系统调用
  • 用户态与内核态切换开销巨大
  • 即使没有竞争也要付出这个代价

实际场景分析

研究发现,大多数情况下:

  • 大部分锁不存在竞争
  • 很多锁总是由同一个线程获取
  • 竞争往往很短暂

针对这些特点,JVM引入了锁升级机制。

对象头与Mark Word

Java对象内存布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────┐
│ Java对象 │
├─────────────────────────────────────────────────────────┤
│ │
│ 对象头(Header) │
│ ├── Mark Word (8字节,64位JVM) │
│ ├── Class Pointer (4/8字节,类型指针) │
│ └── 数组长度 (4字节,仅数组对象) │
│ │
│ 实例数据(Instance Data) │
│ └── 对象的字段数据 │
│ │
│ 对齐填充(Padding) │
│ └── 8字节对齐 │
│ │
└─────────────────────────────────────────────────────────┘

Mark Word详解

Mark Word是实现锁的关键,它的结构会随锁状态变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
64位JVM的Mark Word结构:

┌────────────────────────────────────────────────────────────────────┐
│ Mark Word (64 bits) │
├────────────────────────────────────────────────────────────────────┤
│ │
│ 无锁状态(Normal): │
│ ┌─────────────┬──────────────┬─────────┬────────┬───────┬───────┐│
│ │ unused │ hashcode │ unused │ age │biased │ lock ││
│ │ 25 bits │ 31 bits │ 1 bit │ 4 bits │ 1 bit │ 2 bits││
│ │ │ 对象哈希码 │ │ GC年龄 │ 0 │ 01 ││
│ └─────────────┴──────────────┴─────────┴────────┴───────┴───────┘│
│ │
│ 偏向锁状态(Biased): │
│ ┌────────────────────────────┬─────────┬────────┬───────┬───────┐│
│ │ thread ID │ epoch │ age │biased │ lock ││
│ │ 54 bits │ 2 bits │ 4 bits │ 1 bit │ 2 bits││
│ │ 持有锁的线程ID │ 偏向时间│ GC年龄 │ 1 │ 01 ││
│ └────────────────────────────┴─────────┴────────┴───────┴───────┘│
│ │
│ 轻量级锁状态(Lightweight Locked): │
│ ┌──────────────────────────────────────────────────────────┬─────┐│
│ │ ptr_to_lock_record │lock ││
│ │ 62 bits │2bits││
│ │ 指向栈中Lock Record的指针 │ 00 ││
│ └──────────────────────────────────────────────────────────┴─────┘│
│ │
│ 重量级锁状态(Heavyweight Locked): │
│ ┌──────────────────────────────────────────────────────────┬─────┐│
│ │ ptr_to_heavyweight_monitor │lock ││
│ │ 62 bits │2bits││
│ │ 指向Monitor对象的指针 │ 10 ││
│ └──────────────────────────────────────────────────────────┴─────┘│
│ │
│ GC标记状态: │
│ ┌──────────────────────────────────────────────────────────┬─────┐│
│ │ (空) │lock ││
│ │ │ 11 ││
│ └──────────────────────────────────────────────────────────┴─────┘│
│ │
└────────────────────────────────────────────────────────────────────┘

锁状态标志位:
biased_lock | lock | 状态
──────────────────────────
0 | 01 | 无锁
1 | 01 | 偏向锁
- | 00 | 轻量级锁
- | 10 | 重量级锁
- | 11 | GC标记

锁升级全过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────────┐
│ 锁升级过程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 无锁 │
│ │ │
│ │ 第一个线程访问 │
│ ▼ │
│ 偏向锁 ─────────────────────────────────────────┐ │
│ │ │ │
│ │ 出现第二个线程竞争 │ │
│ ▼ │ │
│ 轻量级锁 ─────────────────────────┐ │ │
│ │ │ │ │
│ │ 自旋失败/竞争激烈 │ │ │
│ ▼ │ │ │
│ 重量级锁 │ │ │
│ │ │ │
│ │ │ │
│ 注意:锁只能升级,不能降级 │ │ │
│ (偏向锁批量重偏向除外) │ │ │
│ │ │ │
└────────────────────────────────────────┴──────────────┴─────────────┘

偏向锁(Biased Locking)

设计思想

统计表明,大多数情况下锁不仅不存在竞争,而且总是由同一个线程多次获得。偏向锁的目标是:让这个线程获取锁的代价尽可能低。

偏向锁原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
首次获取锁:
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 线程A第一次进入同步块 │
│ │ │
│ ▼ │
│ 检查Mark Word的锁状态 │
│ │ │
│ ▼ │
│ biased=1, lock=01, threadID=0 (可偏向但未偏向) │
│ │ │
│ ▼ │
│ CAS将threadID设为当前线程ID │
│ │ │
│ ┌────┴────┐ │
│ 成功 失败 │
│ │ │ │
│ ▼ ▼ │
│ 获得偏向锁 说明有竞争, │
│ 升级为轻量级锁 │
│ │
└─────────────────────────────────────────────────────────────────────┘

再次获取锁(同一线程):
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ 线程A再次进入同步块 │
│ │ │
│ ▼ │
│ 检查Mark Word │
│ │ │
│ ▼ │
│ threadID == 当前线程ID ? │
│ │ │
│ 是 │
│ │ │
│ ▼ │
│ 直接进入同步块(无需任何同步操作!) │
│ │
└─────────────────────────────────────────────────────────────────────┘

偏向锁的性能优势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 同一线程多次获取锁
synchronized (lock) {
// 第一次:CAS设置threadID
}

synchronized (lock) {
// 第二次:只检查threadID,无CAS
}

synchronized (lock) {
// 第三次:只检查threadID,无CAS
}

// 性能:几乎等同于无锁!

偏向锁的撤销

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌─────────────────────────────────────────────────────────────────────┐
│ 偏向锁撤销过程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 线程B尝试获取被线程A偏向的锁 │
│ │ │
│ ▼ │
│ 等待全局安全点(Safe Point) │
│ (所有线程暂停,类似GC) │
│ │ │
│ ▼ │
│ 检查线程A的状态 │
│ │ │
│ ┌────┴────┐ │
│ 线程A已退出 线程A还在 │
│ 同步块 同步块中 │
│ │ │ │
│ ▼ ▼ │
│ 将锁对象设为 升级为轻量级锁 │
│ 无锁状态, 线程A持有轻量级锁 │
│ 然后线程B 线程B自旋等待 │
│ 重新竞争 │
│ │
└─────────────────────────────────────────────────────────────────────┘

偏向锁撤销的代价很高(需要STW),所以JVM有以下优化:

批量重偏向(Bulk Rebias)

1
2
3
4
5
6
7
8
9
10
如果一个类的对象被多个线程交替访问:

对象1 偏向线程A → 撤销 → 偏向线程B
对象2 偏向线程A → 撤销 → 偏向线程B
对象3 偏向线程A → 撤销 → 偏向线程B
...

当撤销次数达到阈值(默认20),JVM会:
- 批量将该类所有对象重偏向到当前线程
- 更新类的epoch值

批量撤销(Bulk Revoke)

1
2
3
4
如果撤销次数继续增加(默认40),JVM会:
- 禁用该类的偏向锁功能
- 将该类所有对象的偏向标记设为0
- 之后该类对象直接使用轻量级锁

偏向锁的JVM参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 开启偏向锁(JDK 15之前默认开启)
-XX:+UseBiasedLocking

# 关闭偏向锁
-XX:-UseBiasedLocking

# 偏向锁启动延迟(默认4秒)
-XX:BiasedLockingStartupDelay=0

# 批量重偏向阈值
-XX:BiasedLockingBulkRebiasThreshold=20

# 批量撤销阈值
-XX:BiasedLockingBulkRevokeThreshold=40

注意:JDK 15开始,偏向锁默认关闭,JDK 18+已废弃偏向锁。

轻量级锁(Lightweight Locking)

设计思想

当存在轻微竞争时,使用CAS自旋代替操作系统互斥量,避免内核态切换。

轻量级锁原理

Lock Record(锁记录)

1
2
3
4
5
6
7
8
9
10
11
线程栈帧中的Lock Record结构:

┌─────────────────────────────────┐
│ Lock Record │
├─────────────────────────────────┤
│ Displaced Mark Word │ ← 保存对象原来的Mark Word
│ (用于恢复) │
├─────────────────────────────────┤
│ Owner (ptr to object) │ ← 指向锁对象
│ │
└─────────────────────────────────┘

加锁过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
┌─────────────────────────────────────────────────────────────────────┐
│ 轻量级锁加锁过程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 步骤1:在栈帧中创建Lock Record │
│ │
│ 线程栈 锁对象 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Lock Record │ │ Mark Word │ │
│ │┌───────────┐│ │ (无锁状态) │ │
│ ││Displaced ││ │ hashcode │ │
│ ││Mark Word ││ │ age | 0 |01 │ │
│ │└───────────┘│ └─────────────┘ │
│ │┌───────────┐│ │
│ ││ owner ───┼┼────────────────────▶ │
│ │└───────────┘│ │
│ └─────────────┘ │
│ │
│ 步骤2:复制Mark Word到Lock Record │
│ │
│ 线程栈 锁对象 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Lock Record │ │ Mark Word │ │
│ │┌───────────┐│ 复制 │ hashcode │ │
│ ││ hashcode ││◀───────────────│ age | 0 |01 │ │
│ ││ age|0|01 ││ └─────────────┘ │
│ │└───────────┘│ │
│ └─────────────┘ │
│ │
│ 步骤3:CAS替换Mark Word为指向Lock Record的指针 │
│ │
│ 线程栈 锁对象 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Lock Record │◀───────────────│ Mark Word │ │
│ │┌───────────┐│ CAS │ptr_to_LR|00 │ │
│ ││ hashcode ││ └─────────────┘ │
│ ││ age|0|01 ││ │
│ │└───────────┘│ │
│ └─────────────┘ │
│ │
│ CAS成功:获得轻量级锁,lock位变为00 │
│ CAS失败:存在竞争,进入自旋或升级 │
│ │
└─────────────────────────────────────────────────────────────────────┘

解锁过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────────────────────────────┐
│ 轻量级锁解锁过程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CAS将Displaced Mark Word替换回对象头 │
│ │
│ 线程栈 锁对象 │
│ ┌─────────────┐ ┌─────────────┐ │
│ │Lock Record │ │ Mark Word │ │
│ │┌───────────┐│ CAS │ptr_to_LR|00 │ │
│ ││ hashcode ││───────────────▶│ │ │
│ ││ age|0|01 ││ (还原) │ hashcode │ │
│ │└───────────┘│ │ age | 0 |01 │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ CAS成功:解锁成功,恢复无锁状态 │
│ CAS失败:说明有其他线程在等待,锁已膨胀为重量级锁 │
│ 需要在释放锁的同时唤醒等待的线程 │
│ │
└─────────────────────────────────────────────────────────────────────┘

自旋优化

当CAS失败时,线程不会立即阻塞,而是自旋等待:

1
2
3
4
5
6
7
8
9
10
11
// 自旋伪代码
int spinCount = 0;
while (!cas_acquire_lock()) {
spinCount++;
if (spinCount > MAX_SPIN_COUNT) {
// 自旋次数过多,升级为重量级锁
inflate_to_heavyweight();
break;
}
// 继续自旋(空转CPU)
}

自适应自旋(Adaptive Spinning)

JDK 6引入自适应自旋,JVM会根据历史数据动态调整自旋次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────┐
│ 自适应自旋策略 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 如果上次在这个锁上自旋成功了: │
│ → 本次允许更长的自旋时间 │
│ → JVM认为这个锁很快会被释放 │
│ │
│ 如果上次在这个锁上自旋失败了: │
│ → 本次减少自旋时间,甚至跳过自旋 │
│ → JVM认为自旋可能是浪费CPU │
│ │
│ 如果锁的拥有者正在运行中: │
│ → 倾向于自旋(锁可能很快释放) │
│ │
│ 如果锁的拥有者已经阻塞了: │
│ → 不自旋,直接阻塞 │
│ │
└─────────────────────────────────────────────────────────────────────┘

重量级锁(Heavyweight Locking)

设计思想

当竞争激烈、自旋无效时,使用操作系统的互斥量实现真正的阻塞等待。

Monitor对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌─────────────────────────────────────────────────────────────────────┐
│ ObjectMonitor 结构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ObjectMonitor │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ _header : Mark Word (保存原来的对象头) │ │
│ │ _object : 指向锁对象 │ │
│ │ _owner : 当前持有锁的线程 │ │
│ │ _recursions : 重入次数 │ │
│ │ _count : 等待线程数 │ │
│ │ │ │
│ │ _EntryList : 阻塞等待获取锁的线程队列 │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ T1 │ │ T2 │ │ T3 │ 等待获取锁 │ │
│ │ └────┘ └────┘ └────┘ │ │
│ │ │ │
│ │ _WaitSet : 调用wait()后等待的线程队列 │ │
│ │ ┌────┐ ┌────┐ │ │
│ │ │ T4 │ │ T5 │ 等待被notify唤醒 │ │
│ │ └────┘ └────┘ │ │
│ │ │ │
│ │ _cxq : 最近到达的竞争者队列(ContentionQueue) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

锁膨胀过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────────────┐
│ 轻量级锁膨胀为重量级锁 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 线程A持有轻量级锁 │
│ │ │
│ ▼ │
│ 线程B尝试获取锁,CAS失败 │
│ │ │
│ ▼ │
│ 线程B自旋等待 │
│ │ │
│ ▼ │
│ 自旋次数超过阈值,仍未获取到锁 │
│ │ │
│ ▼ │
│ 触发锁膨胀(inflate) │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 1. 分配ObjectMonitor对象 │ │
│ │ 2. 将对象原来的Mark Word保存到Monitor的_header字段 │ │
│ │ 3. 设置Monitor的_owner为当前锁的持有者(线程A) │ │
│ │ 4. 将对象的Mark Word替换为指向Monitor的指针 │ │
│ │ 5. 锁标志位设为10 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 线程B进入EntryList阻塞等待 │
│ │ │
│ ▼ │
│ 线程A释放锁时,唤醒EntryList中的线程 │
│ │
└─────────────────────────────────────────────────────────────────────┘

重量级锁的工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
┌─────────────────────────────────────────────────────────────────────┐
│ 重量级锁的获取与释放 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 获取锁: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ if (_owner == NULL) { │ │
│ │ // 锁空闲,CAS获取 │ │
│ │ CAS(_owner, NULL, currentThread); │ │
│ │ } else if (_owner == currentThread) { │ │
│ │ // 重入 │ │
│ │ _recursions++; │ │
│ │ } else { │ │
│ │ // 竞争失败 │ │
│ │ 1. 先自旋尝试 │ │
│ │ 2. 自旋失败则加入_cxq队列 │ │
│ │ 3. 调用park()阻塞 │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 释放锁: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ if (_recursions > 0) { │ │
│ │ // 还在重入中 │ │
│ │ _recursions--; │ │
│ │ return; │ │
│ │ } │ │
│ │ _owner = NULL; │ │
│ │ // 唤醒等待线程 │ │
│ │ 从_EntryList或_cxq中取出一个线程 │ │
│ │ unpark(waitingThread); │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

其他锁优化

锁消除(Lock Elimination)

JIT编译器通过逃逸分析,消除不可能存在竞争的锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 原始代码
public String concat(String s1, String s2) {
StringBuffer sb = new StringBuffer(); // StringBuffer是线程安全的
sb.append(s1);
sb.append(s2);
return sb.toString();
}

// sb不会逃逸出方法,不可能被其他线程访问
// JIT优化后,移除所有synchronized
public String concat(String s1, String s2) {
// 等价于使用StringBuilder(无锁)
StringBuilder sb = new StringBuilder();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
1
2
3
4
5
6
7
8
# 开启锁消除(默认开启)
-XX:+EliminateLocks

# 关闭锁消除
-XX:-EliminateLocks

# 开启逃逸分析(锁消除的前提)
-XX:+DoEscapeAnalysis

锁粗化(Lock Coarsening)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 原始代码
for (int i = 0; i < 10000; i++) {
synchronized (lock) {
// 操作1
}
// 无关操作
synchronized (lock) {
// 操作2
}
}

// JIT优化后
synchronized (lock) { // 扩大锁的范围
for (int i = 0; i < 10000; i++) {
// 操作1
// 无关操作
// 操作2
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 另一个例子
public void method() {
synchronized (lock) { doSomething1(); }
synchronized (lock) { doSomething2(); }
synchronized (lock) { doSomething3(); }
}

// 优化后
public void method() {
synchronized (lock) {
doSomething1();
doSomething2();
doSomething3();
}
}

锁升级流程总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
┌─────────────────────────────────────────────────────────────────────┐
│ 完整的锁升级流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ 无锁 │ │
│ │ 001(01) │ │
│ └────┬────┘ │
│ │ │
│ 第一个线程访问 │
│ (延迟4秒后启用偏向) │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │ 偏向锁 │ │
│ │ 101(01) │ │
│ └────┬────┘ │
│ │ │
│ ┌─────────┼─────────┐ │
│ │ │ │ │
│ 同一线程 其他线程竞争 批量撤销 │
│ 再次访问 (偏向锁撤销) 超过阈值 │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 直接进入 ┌─────────┐ 禁用该类 │
│ (无CAS) │轻量级锁 │ 偏向锁 │
│ │ 00 │ │
│ └────┬────┘ │
│ │ │
│ ┌─────────┼─────────┐ │
│ │ │ │ │
│ CAS成功 CAS失败 自旋成功 │
│ │ (自旋) │ │
│ ▼ │ ▼ │
│ 获得锁 │ 获得锁 │
│ │ │
│ 自旋失败 │
│ (超过阈值) │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │重量级锁 │ │
│ │ 10 │ │
│ └─────────┘ │
│ │
│ 说明:括号内为 biased_lock位 + lock位 │
│ │
└─────────────────────────────────────────────────────────────────────┘

各种锁的对比

锁类型 优点 缺点 适用场景
偏向锁 无竞争时几乎无开销 撤销需要STW 单线程访问
轻量级锁 无阻塞,响应快 自旋消耗CPU 竞争少,同步块执行快
重量级锁 不消耗CPU 阻塞,上下文切换开销 竞争激烈

JVM调优参数汇总

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 偏向锁相关
-XX:+UseBiasedLocking # 开启偏向锁
-XX:-UseBiasedLocking # 关闭偏向锁
-XX:BiasedLockingStartupDelay=0 # 偏向锁启动延迟(默认4000ms)
-XX:BiasedLockingBulkRebiasThreshold=20 # 批量重偏向阈值
-XX:BiasedLockingBulkRevokeThreshold=40 # 批量撤销阈值

# 自旋相关
-XX:PreBlockSpin=10 # 自旋次数(已被自适应自旋取代)

# 锁优化相关
-XX:+EliminateLocks # 开启锁消除
-XX:+DoEscapeAnalysis # 开启逃逸分析

# 调试相关
-XX:+PrintBiasedLockingStatistics # 打印偏向锁统计
-XX:+TraceBiasedLocking # 跟踪偏向锁

总结

JVM对synchronized的优化是分层的:

  1. 偏向锁:针对无竞争场景,只需一次CAS,之后几乎无开销
  2. 轻量级锁:针对轻微竞争,通过CAS自旋避免阻塞
  3. 重量级锁:针对激烈竞争,使用操作系统互斥量

配合锁消除、锁粗化等优化,synchronized在大多数场景下性能已经很好,是Java并发编程的首选同步方式。