JUC 锁的实现与对比
2025/12/19大约 5 分钟约 1632 字
基于 JDK 1.8 源码的 JUC 锁核心原理分析。
一、ReentrantLock(可重入锁)
1.1 核心定义与 AQS 映射
定位
ReentrantLock 是独占模式(Exclusive)的可重入互斥锁,基于 AQS 实现,是 synchronized 关键字的替代方案。
State 的含义
// AQS 中
private volatile int state;| state 值 | 含义 |
|---|---|
| 0 | 锁空闲,未被任何线程持有 |
| 1 | 锁被某线程获取 1 次 |
| N (N > 1) | 锁被同一线程重入 N-1 次(总获取 N 次) |
核心逻辑:
- 获取锁:
state从 0 变为 1,或者从 N 变为 N+1(重入) - 释放锁:
state从 N 变为 N-1,只有归零时才真正释放
Owner 的维护
// AbstractOwnableSynchronizer
private transient Thread exclusiveOwnerThread;作用:记录当前持有锁的线程,是实现"可重入"的关键。
// 可重入判断逻辑
if (current == getExclusiveOwnerThread()) {
// 是持锁线程,允许重入
setState(c + 1);
return true;
}1.2 内部架构:Sync 家族
类结构
ReentrantLock
└── Sync (abstract, extends AQS)
├── NonfairSync (非公平锁实现)
└── FairSync (公平锁实现)public class ReentrantLock implements Lock {
private final Sync sync; // 持有 Sync 引用
// 抽象内部类,继承 AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 公共方法:nonfairTryAcquire, tryRelease
}
// 非公平锁实现
static final class NonfairSync extends Sync {
final void lock() { /* ... */ }
protected final boolean tryAcquire(int acquires) { /* ... */ }
}
// 公平锁实现
static final class FairSync extends Sync {
final void lock() { /* ... */ }
protected final boolean tryAcquire(int acquires) { /* ... */ }
}
// 构造函数
public ReentrantLock() {
sync = new NonfairSync(); // 默认非公平
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}为什么使用内部类 Sync 继承 AQS?
| 原因 | 说明 |
|---|---|
| 封装性 | 隐藏 AQS 的复杂实现细节,只暴露 Lock 接口方法 |
| 模板模式 | Sync 作为模板类提供公共逻辑,子类实现差异部分 |
| 灵活性 | 通过组合而非继承,允许 ReentrantLock 实现多个接口 |
如果直接继承 AQS,会暴露 acquire()、release() 等方法给用户,破坏封装性。
1.3 核心考点:公平锁 vs 非公平锁
非公平锁(默认)
"插队"逻辑:新线程不管队列里有没有人等待,直接尝试 CAS 抢锁。
// NonfairSync.lock()
final void lock() {
// 第一次插队:直接 CAS 抢锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 失败则走 AQS 流程
}
// NonfairSync.tryAcquire() -> nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 第二次插队:直接 CAS,不检查队列
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 可重入
setState(c + acquires);
return true;
}
return false;
}为什么吞吐量高?
减少了上下文切换。当锁释放时,如果新线程直接抢到锁,就不需要唤醒队列中的线程(避免一次 park/unpark 开销)。
公平锁
"排队"逻辑:获取锁前先检查队列,有人排队就乖乖到队尾。
// FairSync.lock()
final void lock() {
acquire(1); // 没有第一次插队尝试
}
// FairSync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 关键差异:先检查队列!
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
setState(c + acquires);
return true;
}
return false;
}hasQueuedPredecessors() 方法:
// 判断队列中是否有比当前线程等待更久的节点
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}源码差异对比
| 差异点 | 非公平锁 | 公平锁 |
|---|---|---|
| lock() 入口 | 先 CAS 抢一次 | 直接 acquire(1) |
| tryAcquire() 中 | if (c == 0) 直接 CAS | if (!hasQueuedPredecessors() && c == 0) |
一行代码的核心差异:
// 非公平锁
if (compareAndSetState(0, acquires)) { ... }
// 公平锁
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { ... }对比总结
| 维度 | 非公平锁 | 公平锁 |
|---|---|---|
| 获取策略 | 直接 CAS 抢 | 先查队列 |
| 吞吐量 | 高 | 低 |
| 线程饥饿 | 可能 | 不会 |
| 上下文切换 | 少 | 多 |
| 默认选择 | ✓ | |
| 适用场景 | 追求性能 | 追求公平性 |
1.4 源码级流程解析
加锁流程
lock()
│
▼
sync.lock() ─────────────────────────────────────────────┐
│ │
├── [非公平锁] CAS(0, 1) 成功? │
│ └── 是 → setExclusiveOwnerThread() → 返回 │
│ │
▼ │
acquire(1) ← AQS 模板方法 │
│ │
▼ │
tryAcquire(1) ← 子类实现 │
│ │
├── state == 0? │
│ ├── [非公平] 直接 CAS │
│ └── [公平] hasQueuedPredecessors() + CAS │
│ │
├── current == owner?→ state++ (重入) │
│ │
└── 返回 false │
│ │
▼ │
addWaiter() → acquireQueued() → park() │
│
─┘解锁流程
unlock()
│
▼
sync.release(1) ← AQS 模板方法
│
▼
tryRelease(1) ← Sync 实现
│
├── 检查 current == owner(必须是持锁线程)
│
├── c = state - 1
│
├── c == 0?
│ ├── 是 → setExclusiveOwnerThread(null) → 返回 true
│ └── 否 → setState(c) → 返回 false(还有重入未释放)
│
▼
true → unparkSuccessor(head) ← 唤醒后继节点tryRelease 源码:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 必须是持锁线程才能释放
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// state 归零,真正释放锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free; // 只有 true 时才会唤醒后继
}1.5 高级特性与对比
ReentrantLock vs synchronized
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现机制 | JVM 内置(Monitor) | JDK 实现(AQS) |
| 锁释放 | 自动(出作用域) | 手动(finally unlock) |
| 可中断获取 | 不支持 | lockInterruptibly() |
| 超时获取 | 不支持 | tryLock(timeout) |
| 公平锁 | 不支持 | 构造时指定 |
| 条件队列 | 1 个(wait/notify) | 多个 Condition |
| 锁状态查询 | 不支持 | isLocked() 等 |
| 性能 | JDK6 后优化,相当 | 相当 |
Condition 的价值(精准唤醒)
// synchronized 只有一个等待队列
synchronized (obj) {
obj.wait(); // 所有线程在同一个队列
obj.notifyAll(); // 唤醒所有,无法精准控制
}
// ReentrantLock 可以有多个 Condition
ReentrantLock lock = new ReentrantLock();
Condition producerCond = lock.newCondition();
Condition consumerCond = lock.newCondition();
lock.lock();
try {
producerCond.await(); // 生产者等待
consumerCond.signal(); // 精准唤醒消费者
} finally {
lock.unlock();
}lockInterruptibly 的价值(响应中断)
// synchronized 无法响应中断
synchronized (obj) {
// 如果在等待锁时被中断,只能继续等待
}
// ReentrantLock 可以响应中断
try {
lock.lockInterruptibly(); // 等待时可被中断
} catch (InterruptedException e) {
// 处理中断,避免死锁
}1.6 最佳实践代码
标准模板
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 获取锁(阻塞)
try {
// 临界区代码
// ...
} finally {
lock.unlock(); // 必须在 finally 中释放锁
}为什么 unlock 必须在 finally 中?
// 错误示例:不使用 finally
lock.lock();
doSomething(); // 如果抛异常,锁永远不会释放!
lock.unlock();
// 正确示例:使用 finally
lock.lock();
try {
doSomething();
} finally {
lock.unlock(); // 无论是否异常,都会执行
}tryLock 模板(带超时)
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 获取锁成功
} finally {
lock.unlock();
}
} else {
// 获取锁失败(超时或被中断)
// 执行降级逻辑
}可中断获取模板
try {
lock.lockInterruptibly();
try {
// 临界区代码
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 响应中断,避免死锁
Thread.currentThread().interrupt();
}1.7 总结
| 知识点 | 要点 |
|---|---|
| AQS 映射 | 独占模式,state 表示重入次数 |
| 可重入原理 | exclusiveOwnerThread 判断 + state 递增 |
| 公平 vs 非公平 | 一行代码差异:hasQueuedPredecessors() |
| 释放锁 | state 归零才真正释放 |
| vs synchronized | 更灵活:可中断、超时、多 Condition |
| 最佳实践 | lock-try-finally-unlock |
