JUC-ReentrantLock实现原理

ReentrantLock 深度剖析

ReentrantLock 是 JUC 包中最重要的锁实现,理解其原理对于掌握 Java 并发编程至关重要。

一、ReentrantLock 概述

ReentrantLock 是基于 AQS(AbstractQueuedSynchronizer)实现的可重入独占锁,相比 synchronized 提供了更丰富的功能特性。

1.1 核心特性

特性说明
可重入同一线程可多次获取锁,通过 state 计数器实现
公平/非公平可选择按 FIFO 顺序获取锁或允许插队
可中断支持 lockInterruptibly() 响应中断
超时获取支持 tryLock(timeout) 限时等待
多条件变量支持多个 Condition 队列

1.2 内部结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync; // 同步器

abstract static class Sync extends AbstractQueuedSynchronizer {
// 核心同步逻辑
}

static final class NonfairSync extends Sync {
// 非公平锁实现
}

static final class FairSync extends Sync {
// 公平锁实现
}
}

依赖的核心组件:

  • state 变量:表示锁的持有状态和重入次数
  • 同步队列(CLH 队列):双向链表,存放等待获取锁的线程
  • 等待队列(Condition 队列):单向链表,存放调用 await() 的线程
1
2
3
4
5
6
7
8
              +-----------+     +-----------+     +-----------+
同步队列: | head | <-> | node1 | <-> | tail |
+-----------+ +-----------+ +-----------+
|
v (signal)
+-----------+ +-----------+
等待队列: | firstWait | --> | lastWait |
+-----------+ +-----------+

二、源码深度分析

2.1 加锁流程(以非公平锁为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// java.util.concurrent.locks.ReentrantLock.NonfairSync
final void lock() {
// 1. 先尝试 CAS 直接获取锁(非公平的体现:直接插队)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 2. CAS 失败,进入 AQS 的 acquire 流程
acquire(1);
}

// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
// 3. tryAcquire 尝试获取锁
// 4. 失败则 addWaiter 加入同步队列
// 5. acquireQueued 自旋 + 阻塞等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

2.2 tryAcquire 实现对比

非公平锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}
}
// 可重入:已持有锁则累加 state
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

公平锁:

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
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;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

// 检查是否有前驱节点(即是否有线程在等待)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

2.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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 将当前线程封装成 Node 加入同步队列尾部
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
// CAS 设置尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// CAS 失败或队列未初始化,自旋入队
enq(node);
return node;
}

// 自旋 + 阻塞等待获取锁
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 前驱是 head 时尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 判断是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // LockSupport.park() 阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

2.4 解锁流程

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
public void unlock() {
sync.release(1);
}

// AQS.release
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒后继节点
return true;
}
return false;
}

// ReentrantLock.Sync.tryRelease
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state 减为 0 时才真正释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

三、公平锁 vs 非公平锁

对比维度公平锁非公平锁
获取顺序FIFO,按入队顺序允许插队
吞吐量较低(频繁切换)较高(减少切换)
饥饿问题不会饥饿可能饥饿
适用场景对公平性要求高追求性能(默认)

为什么默认是非公平锁?

非公平锁在线程刚释放锁时,新来的线程可能直接获取锁,避免了唤醒队列中线程的开销,减少了线程上下文切换,吞吐量更高。

1
2
3
4
5
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);

// 创建非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock(false);

四、Condition 条件变量

Condition 提供了类似 Object.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
ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();

// 生产者
public void put(E e) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 满了就等待
items[putIndex] = e;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal(); // 通知消费者
} finally {
lock.unlock();
}
}

// 消费者
public E take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 空了就等待
E e = items[takeIndex];
if (++takeIndex == items.length) takeIndex = 0;
count--;
notFull.signal(); // 通知生产者
return e;
} finally {
lock.unlock();
}
}

五、CAS 与自旋锁

5.1 CAS 原理

CAS(Compare And Swap)是一种硬件级别的原子操作,底层依赖 CPU 的 cmpxchg 指令。

1
2
3
// Unsafe 类提供的 CAS 操作
public final native boolean compareAndSwapInt(
Object obj, long offset, int expect, int update);

三个操作数:

  • V:内存值
  • E:期望值
  • N:新值

执行逻辑:若 V == E,则 V = N,返回 true;否则返回 false。

5.2 自旋锁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SpinLock {
private final AtomicReference<Thread> owner = new AtomicReference<>();

public void lock() {
Thread current = Thread.currentThread();
// 自旋等待
while (!owner.compareAndSet(null, current)) {
// 可添加 yield() 或短暂休眠降低 CPU 消耗
Thread.yield();
}
}

public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}

自旋锁的优缺点:

优点缺点
避免线程上下文切换锁饥饿:某线程可能一直抢不到锁
适合短时间锁持有总线风暴:多核 CAS 同一变量导致缓存一致性开销

5.3 AQS 中的自适应自旋

AQS 在 acquireQueued 中会先自旋一定次数再阻塞,自旋次数由 JVM 根据历史成功率动态调整。

六、ReentrantLock vs synchronized

特性synchronizedReentrantLock
实现层面JVM 内置JDK 实现
锁获取释放隐式(自动)显式(手动)
可中断获取不支持lockInterruptibly()
超时获取不支持tryLock(timeout)
公平锁不支持支持
条件变量单一多个 Condition
锁状态查询不支持isLocked(), getQueueLength()
性能JDK 6+ 优化后相当相当

七、最佳实践

7.1 标准使用模板

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

public void doSomething() {
lock.lock(); // 获取锁
try {
// 业务逻辑
} finally {
lock.unlock(); // 必须在 finally 中释放锁
}
}

7.2 使用 tryLock 避免死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean transferMoney(Account from, Account to, int amount) {
while (true) {
if (from.lock.tryLock()) {
try {
if (to.lock.tryLock()) {
try {
// 执行转账
return true;
} finally {
to.lock.unlock();
}
}
} finally {
from.lock.unlock();
}
}
// 随机等待后重试,避免活锁
Thread.sleep(new Random().nextInt(10));
}
}

7.3 选择建议

  • 优先 synchronized:简单场景,JVM 自动优化
  • 使用 ReentrantLock:需要可中断、超时、公平锁、多条件变量等高级特性