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

synchronized和ReentrantLock是Java中最常用的两种锁机制。本文深入分析它们的底层原理、使用方法和适用场景。

synchronized关键字

synchronized是Java的内置锁,也叫监视器锁(Monitor Lock)或内置监视器。

使用方式

1. 修饰实例方法

1
2
3
4
5
6
7
8
9
10
11
12
public class Counter {
private int count = 0;

// 锁的是this对象
public synchronized void increment() {
count++;
}

public synchronized int getCount() {
return count;
}
}

2. 修饰静态方法

1
2
3
4
5
6
7
8
public class Counter {
private static int count = 0;

// 锁的是Class对象
public static synchronized void increment() {
count++;
}
}

3. 同步代码块

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
public class Counter {
private int count = 0;
private final Object lock = new Object();

public void increment() {
// 锁指定对象
synchronized (lock) {
count++;
}
}

public void decrement() {
// 锁this
synchronized (this) {
count--;
}
}

public static void staticMethod() {
// 锁Class对象
synchronized (Counter.class) {
// ...
}
}
}

底层原理:Monitor

每个Java对象都关联一个Monitor(监视器),synchronized就是通过Monitor实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────┐
│ Monitor │
├─────────────────────────────────────────────┤
│ Owner: 当前持有锁的线程 │
│ │
│ EntryList: 等待获取锁的线程队列 │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │ T1 │ │ T2 │ │ T3 │ 阻塞等待 │
│ └────┘ └────┘ └────┘ │
│ │
│ WaitSet: 调用wait()后等待的线程 │
│ ┌────┐ ┌────┐ │
│ │ T4 │ │ T5 │ 等待被notify唤醒 │
│ └────┘ └────┘ │
│ │
│ count: 重入计数 │
└─────────────────────────────────────────────┘

字节码层面

1
2
3
4
5
public void method() {
synchronized (this) {
// 临界区代码
}
}

编译后的字节码:

1
2
3
4
5
monitorenter     // 获取锁,count+1
// 临界区代码
monitorexit // 释放锁,count-1
// 异常处理
monitorexit // 确保异常时也释放锁

对象头与锁标记

Java对象在内存中的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────────────────────┐
│ 对象头 │
├───────────────────────────┬─────────────────────────────┤
│ Mark Word │ Class Pointer │
│ (锁信息、GC等) │ (指向类元数据) │
├───────────────────────────┴─────────────────────────────┤
│ 实例数据 │
├─────────────────────────────────────────────────────────┤
│ 对齐填充 │
└─────────────────────────────────────────────────────────┘

Mark Word (64位JVM):
┌────────────────────────────────────────────────────────┐
│ 锁状态 │ 25bit │ 31bit │ 1bit │ 4bit │ 1bit │ 2bit │
├──────────┼────────┼────────────┼───────┼──────┼───────┼──────┤
│ 无锁 │ unused │ hashcode │ unused│ age │ biased│ 01 │
│ 偏向锁 │ threadID (54bit) │ epoch │ age │ biased│ 01 │
│ 轻量级锁 │ 指向栈中锁记录的指针 (62bit) │ 00 │
│ 重量级锁 │ 指向Monitor的指针 (62bit) │ 10 │
│ GC标记 │ (62bit) │ 11 │
└────────────────────────────────────────────────────────┘

锁升级过程

JDK 6之后,synchronized引入了锁升级机制,从偏向锁→轻量级锁→重量级锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────────┐
│ 锁升级过程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 无锁 ──▶ 偏向锁 ──▶ 轻量级锁 ──▶ 重量级锁 │
│ │ │ │ │
│ │ │ │ │
│ 单线程访问 有竞争但 竞争激烈 │
│ 无竞争 交替执行 同时竞争 │
│ │
│ 升级是单向的,不会降级(偏向锁例外) │
│ │
└─────────────────────────────────────────────────────────────────┘

1. 偏向锁(Biased Locking)

适用场景:只有一个线程访问同步块。

1
2
3
4
5
6
7
8
9
10
线程A第一次获取锁:
┌─────────────────┐
│ Mark Word │
│ ThreadID = A │ 记录线程A的ID
│ biased = 1 │
│ lock = 01 │
└─────────────────┘

线程A再次获取锁:
检查ThreadID == A?是 → 直接进入(无需CAS)

偏向锁只需要在第一次获取时进行CAS操作,之后同一线程进入同步块只需检查ThreadID,几乎无开销。

2. 轻量级锁(Lightweight Locking)

适用场景:多个线程交替访问同步块(无实际竞争)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
线程A获取轻量级锁:

1. 在栈帧中创建Lock Record
┌──────────────────┐
│ Lock Record │
│ ┌──────────────┐ │
│ │Displaced Mark│ │ ← 保存对象原来的Mark Word
│ │ Word │ │
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │ owner │─┼──▶ 指向锁对象
│ └──────────────┘ │
└──────────────────┘

2. CAS将对象Mark Word替换为指向Lock Record的指针
┌─────────────────┐
│ Mark Word │
│ ptr to LockRec │──▶ 指向线程A栈中的Lock Record
│ lock = 00 │
└─────────────────┘

3. 释放锁时,CAS将Displaced Mark Word还原

3. 重量级锁(Heavyweight Locking)

适用场景:多个线程同时竞争锁。

1
2
3
4
5
6
7
8
9
轻量级锁CAS失败(有竞争)→ 膨胀为重量级锁

┌─────────────────┐
│ Mark Word │
│ ptr to Monitor │──▶ 指向操作系统的Monitor对象
│ lock = 10 │
└─────────────────┘

重量级锁涉及操作系统的Mutex Lock,需要用户态到内核态切换,开销大

锁升级图解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────┐
│ 无锁 │
└──────┬──────┘
│ 第一个线程访问

┌─────────────┐
│ 偏向锁 │ ← 记录线程ID,之后无需CAS
└──────┬──────┘
│ 出现第二个线程竞争

┌─────────────┐
│ 轻量级锁 │ ← CAS自旋尝试获取
└──────┬──────┘
│ 自旋失败/竞争激烈

┌─────────────┐
│ 重量级锁 │ ← 阻塞等待,涉及内核态切换
└─────────────┘

wait/notify机制

synchronized配合Object的wait/notify实现等待通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 10;

public synchronized void produce(int item) throws InterruptedException {
while (queue.size() == capacity) {
wait(); // 队列满,等待消费者消费
}
queue.add(item);
notifyAll(); // 通知消费者
}

public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 队列空,等待生产者生产
}
int item = queue.poll();
notifyAll(); // 通知生产者
return item;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
wait():
1. 释放锁
2. 进入WaitSet等待
3. 被notify唤醒后重新竞争锁

notify():
1. 从WaitSet中唤醒一个线程
2. 被唤醒线程进入EntryList竞争锁

notifyAll():
1. 唤醒WaitSet中所有线程
2. 所有被唤醒线程进入EntryList竞争锁

ReentrantLock

ReentrantLock是java.util.concurrent包提供的可重入锁,功能比synchronized更丰富。

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}

public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}

底层原理:AQS

ReentrantLock基于AQS(AbstractQueuedSynchronizer)实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────────────────────────────┐
│ AQS │
├─────────────────────────────────────────────────────────┤
│ state: int │
│ ├── 0: 锁空闲 │
│ └── >0: 锁被持有(值为重入次数) │
│ │
│ exclusiveOwnerThread: 持有锁的线程 │
│ │
│ CLH队列(双向链表):等待获取锁的线程 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ head │◀──▶│ Node │◀──▶│ Node │◀──▶│ tail │ │
│ │(哨兵)│ │ T1 │ │ T2 │ │ T3 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
└─────────────────────────────────────────────────────────┘

加锁过程(非公平锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ReentrantLock.lock() 简化流程
public void lock() {
// 1. 先尝试CAS直接获取锁(非公平:插队)
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(currentThread);
return;
}

// 2. CAS失败,进入AQS队列等待
acquire(1);
}

// AQS.acquire()
public final void acquire(int arg) {
// tryAcquire再次尝试获取
if (!tryAcquire(arg) &&
// 失败则加入队列并阻塞
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}

加锁流程图

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
lock()


CAS(state: 0→1)成功? ──是──▶ 获得锁,设置owner




tryAcquire()

├─▶ state==0?CAS抢锁

└─▶ owner==当前线程?state++(重入)

失败


addWaiter() 加入CLH队列


acquireQueued() 自旋+阻塞

├─▶ 前驱是head?尝试获取锁

└─▶ 不是head?park()阻塞等待

公平锁 vs 非公平锁

1
2
3
4
5
6
// 非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();
ReentrantLock unfairLock = new ReentrantLock(false);

// 公平锁
ReentrantLock fairLock = new ReentrantLock(true);

非公平锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// NonfairSync
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 直接CAS抢锁,不管队列中是否有等待的线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入逻辑...
return false;
}
1
2
3
4
5
6
7
8
9
10
11
非公平锁:新线程可以插队

队列:head ◀──▶ T1 ◀──▶ T2 ◀──▶ T3

T4到来时:
1. 先CAS尝试获取锁
2. 如果锁刚好释放,T4可能抢到(插队)
3. CAS失败才排队

优点:吞吐量高(减少线程切换)
缺点:可能导致饥饿(T1一直抢不到)

公平锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// FairSync
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 先检查队列中是否有等待的线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 重入逻辑...
return false;
}
1
2
3
4
5
6
7
8
9
10
11
公平锁:严格按顺序获取

队列:head ◀──▶ T1 ◀──▶ T2 ◀──▶ T3

T4到来时:
1. 检查队列是否有等待线程
2. 有等待线程,直接排队
3. 只有队首线程才能获取锁

优点:不会饥饿
缺点:吞吐量低(频繁线程切换)

可中断锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ReentrantLock lock = new ReentrantLock();

Thread t = new Thread(() -> {
try {
// 可中断地获取锁
lock.lockInterruptibly();
try {
// 业务逻辑
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println("获取锁时被中断");
}
});

t.start();
Thread.sleep(1000);
t.interrupt(); // 中断等待中的线程

超时锁

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
ReentrantLock lock = new ReentrantLock();

public void method() {
boolean acquired = false;
try {
// 尝试获取锁,最多等待2秒
acquired = lock.tryLock(2, TimeUnit.SECONDS);
if (acquired) {
// 获取到锁
} else {
// 超时未获取到锁
}
} catch (InterruptedException e) {
// 等待过程中被中断
} finally {
if (acquired) {
lock.unlock();
}
}
}

// 非阻塞尝试
if (lock.tryLock()) {
try {
// 立即获取到锁
} finally {
lock.unlock();
}
} else {
// 没有获取到锁,不阻塞
}

Condition条件变量

Condition是synchronized中wait/notify的替代品,功能更强大。

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
public class BoundedBuffer<T> {
private final Object[] items;
private int putIndex, takeIndex, count;

private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 不满条件
private final Condition notEmpty = lock.newCondition(); // 不空条件

public BoundedBuffer(int capacity) {
items = new Object[capacity];
}

public void put(T item) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // 等待"不满"
}
items[putIndex] = item;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal(); // 通知"不空"
} finally {
lock.unlock();
}
}

@SuppressWarnings("unchecked")
public T take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待"不空"
}
T item = (T) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
count--;
notFull.signal(); // 通知"不满"
return item;
} finally {
lock.unlock();
}
}
}

Condition vs wait/notify

特性 wait/notify Condition
条件数量 1个(对象的WaitSet) 多个(可创建多个Condition)
等待 wait() await()
唤醒一个 notify() signal()
唤醒所有 notifyAll() signalAll()
超时等待 wait(timeout) await(time, unit)
截止时间 不支持 awaitUntil(deadline)
不可中断 不支持 awaitUninterruptibly()

读写锁 ReentrantReadWriteLock

适用于读多写少的场景:

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
51
public class Cache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();

public V get(K key) {
readLock.lock(); // 读锁:可以多线程同时读
try {
return map.get(key);
} finally {
readLock.unlock();
}
}

public void put(K key, V value) {
writeLock.lock(); // 写锁:独占
try {
map.put(key, value);
} finally {
writeLock.unlock();
}
}

public V computeIfAbsent(K key, Function<K, V> loader) {
// 先用读锁检查
readLock.lock();
try {
V value = map.get(key);
if (value != null) {
return value;
}
} finally {
readLock.unlock();
}

// 需要写入,升级为写锁
writeLock.lock();
try {
// 双重检查
V value = map.get(key);
if (value == null) {
value = loader.apply(key);
map.put(key, value);
}
return value;
} finally {
writeLock.unlock();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
读写锁规则:
┌─────────────┬──────────────┬──────────────┐
│ │ 读锁请求 │ 写锁请求 │
├─────────────┼──────────────┼──────────────┤
│ 无锁 │ 允许 │ 允许 │
│ 持有读锁 │ 允许(共享) │ 阻塞 │
│ 持有写锁 │ 阻塞 │ 阻塞(独占) │
└─────────────┴──────────────┴──────────────┘

读-读:不互斥
读-写:互斥
写-写:互斥

synchronized vs ReentrantLock

功能对比

特性 synchronized ReentrantLock
使用方式 关键字 API调用
自动释放 是(退出同步块自动释放) 否(必须手动unlock)
可中断 是(lockInterruptibly)
超时获取 是(tryLock(timeout))
非阻塞获取 是(tryLock())
公平锁 否(只有非公平) 可选(构造参数)
条件变量 1个 多个(newCondition)
可重入
锁升级

性能对比

JDK 6之前,synchronized性能较差。JDK 6引入锁升级后,两者性能相近:

1
2
3
4
5
低竞争场景:
synchronized ≈ ReentrantLock(偏向锁/轻量级锁优化)

高竞争场景:
两者都会退化为重量级锁/阻塞,性能相近

使用建议

1
2
3
4
5
6
7
8
9
10
// 推荐使用synchronized的场景:
// 1. 简单同步,不需要高级功能
public synchronized void simpleMethod() {
// ...
}

// 2. 确保锁一定被释放(不会忘记unlock)
synchronized (lock) {
// 即使抛异常也会自动释放
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 推荐使用ReentrantLock的场景:

// 1. 需要可中断的锁
lock.lockInterruptibly();

// 2. 需要超时获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) { }

// 3. 需要公平锁
new ReentrantLock(true);

// 4. 需要多个条件变量
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

// 5. 需要读写锁
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

最佳实践

1. 减小锁粒度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 不好:锁整个方法
public synchronized void process() {
readFromDB(); // 慢操作
calculate(); // 快操作
writeToFile(); // 慢操作
}

// 好:只锁需要同步的部分
public void process() {
Data data = readFromDB(); // 不需要锁
synchronized (this) {
calculate(data); // 只锁计算部分
}
writeToFile(data); // 不需要锁
}

2. 避免死锁

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
// 可能死锁:不同顺序获取多个锁
void method1() {
synchronized (lockA) {
synchronized (lockB) { }
}
}

void method2() {
synchronized (lockB) { // 顺序相反
synchronized (lockA) { }
}
}

// 解决:固定加锁顺序
void method1() {
synchronized (lockA) {
synchronized (lockB) { }
}
}

void method2() {
synchronized (lockA) { // 相同顺序
synchronized (lockB) { }
}
}

// 或使用tryLock避免死锁
boolean acquired = false;
try {
acquired = lock1.tryLock(1, TimeUnit.SECONDS) &&
lock2.tryLock(1, TimeUnit.SECONDS);
if (acquired) {
// 业务逻辑
}
} finally {
if (acquired) {
lock2.unlock();
lock1.unlock();
}
}

3. 使用try-finally确保释放

1
2
3
4
5
6
7
8
ReentrantLock lock = new ReentrantLock();

lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock(); // 确保释放
}

4. 虚拟线程中使用ReentrantLock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// JDK 21虚拟线程中,synchronized会钉住载体线程
// 推荐使用ReentrantLock

// 不推荐
synchronized (lock) {
Thread.sleep(1000); // 会钉住载体线程
}

// 推荐
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
Thread.sleep(1000); // 虚拟线程可以正常卸载
} finally {
lock.unlock();
}

总结

维度 synchronized ReentrantLock
实现层面 JVM内置,字节码层面 JDK类库,AQS实现
锁类型 非公平 公平/非公平可选
中断支持 不支持 支持
超时支持 不支持 支持
条件变量 单一 多个
锁优化 偏向锁→轻量级→重量级 无(始终AQS队列)
使用复杂度 简单 稍复杂
释放安全 自动释放 需手动释放

选择建议:

  • 简单场景:优先synchronized,简洁不易出错
  • 需要高级功能:使用ReentrantLock
  • JDK 21虚拟线程:优先ReentrantLock
  • 读多写少:使用ReentrantReadWriteLock