一、学习路径
ReentrantLock ├── 1. 怎么用(API + 实战) │ ├── 基本用法 │ ├── 3 种获取锁方式(lock/tryLock/lockInterruptibly) │ ├── 公平 vs 非公平 │ └── Condition 多条件变量 │ ├── 2. 加锁的实现原理(lock() 怎么拿到锁) │ ├── 整体流程:lock → CAS → 入队 → 自旋+park │ ├── AQS 的 tryAcquire 模板方法 │ ├── 入队 addWaiter │ └── 在队列里 acquireQueued(自旋+park) │ └── 3. AQS 原理(底层框架) ├── 3 大要素:state + CLH + LockSupport ├── 模板方法模式 └── 独占 vs 共享
二、如何使用 ReentrantLock
1. 基本用法(替代 synchronized)
// 等价于 synchronized 的方式ReentrantLock lock = new ReentrantLock();public void doWork() { lock.lock(); // 加锁 try { // 临界区 count++; } finally { lock.unlock(); // 必须!且要在 finally 里 }}
vs synchronized:
// synchronizedpublic synchronized void doWork() { count++;}// ReentrantLockpublic void doWork() { lock.lock(); try { count++; } finally { lock.unlock(); }}
关键差异 :ReentrantLock 必须手动 unlock,synchronized 自动释放。
2. 三种获取锁方式
// 方式 1:阻塞获取(不可中断)------ 死等lock.lock();// 方式 2:尝试获取(立刻返回)------ 非阻塞if (lock.tryLock()) { try { // 拿到锁 } finally { lock.unlock(); }} else { // 没拿到,做别的事(降级)}// 方式 3:尝试获取(带超时)------ 限时等待if (lock.tryLock(3, TimeUnit.SECONDS)) { try { // 3 秒内拿到了 } finally { lock.unlock(); }} else { // 超时了}// 方式 4:阻塞获取(可中断)------ 可以被 interrupt 唤醒try { lock.lockInterruptibly(); // ...} catch (InterruptedException e) { // 被中断了}
4 种方式对比:
| 方式 | 阻塞? | 可中断? | 立刻返回? | 适用 |
|---|---|---|---|---|
lock() |
✅ 死等 | ❌ | ❌ | 必须等 |
tryLock() |
❌ | --- | ✅ | 不阻塞 |
tryLock(timeout) |
限时 | --- | 超时后返回 | 限时等待 |
lockInterruptibly() |
✅ | ✅ | ❌ | 可中断 |
3. 公平 vs 非公平
// 默认非公平(性能更好)ReentrantLock unfair = new ReentrantLock();// 公平锁(按 FIFO 顺序获取)ReentrantLock fair = new ReentrantLock(true);
区别:
| 维度 | 非公平(默认) | 公平 |
|---|---|---|
| 性能 | 高(少一次 queue 检查) | 较低(要维护队列) |
| 排队 | 可能插队 | 严格 FIFO |
| 适用 | 大多数场景 | 防止饥饿 |
4. Condition 多条件变量(synchronized 做不到)
// 类 synchronized 的 wait/notify,但可以多个ReentrantLock lock = new ReentrantLock();Condition notEmpty = lock.newCondition();Condition notFull = lock.newCondition();// 等待"不空"lock.lock();try { while (queue.isEmpty()) { notEmpty.await(); // 释放锁 + 阻塞 } // 队列不空了} finally { lock.unlock();}// 通知"不满"lock.lock();try { queue.add(item); notFull.signal(); // 唤醒"不满"的等待者} finally { lock.unlock();}
比 synchronized + wait/notify 高效------因为可以精确唤醒"某种"等待者。
5. 实战:转账(防死锁的 tryLock 模式)
public boolean transfer(Account from, Account to, int amount) { while (true) { if (from.lock.tryLock()) { try { if (to.lock.tryLock()) { // 不会阻塞 try { from.balance -= amount; to.balance += amount; return true; } finally { to.lock.unlock(); } } } finally { from.lock.unlock(); } } // 短暂等待后重试,避免活锁 try { Thread.sleep(10); } catch (Exception e) {} }}
核心 :用 tryLock 而非 lock------避免两个锁相互等待造成死锁。
三、加锁的实现原理(最核心)
1. 整体流程
lock.lock() ↓【第 1 步】CAS state(tryAcquire) ← 一次性拿锁 ↓ 成功拿锁返回 ↓ 失败【第 2 步】addWaiter(入队) ← 进 CLH 队列 ↓【第 3 步】acquireQueued(自旋+park)← 在队列里等 ↓循环: - 看看前驱是不是 head - 是 → 再试一次 CAS state - 不是 → shouldParkAfterFailedAcquire → park ↓被前驱 unpark ↓回到循环顶部
2. 完整源码(精简版)
// ReentrantLock.lockpublic void lock() { sync.acquire(1); // ← 委托给 AQS}// AbstractQueuedSynchronizer.acquire(模板方法)public final void acquire(int arg) { if (!tryAcquire(arg) && // ① 试着拿 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // ② 拿不到就入队 + 自旋 selfInterrupt();}// ReentrantLock.NonfairSync.tryAcquireprotected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires);}final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 锁没被占 if (compareAndSetState(0, acquires)) { // CAS 抢锁 setExclusiveOwnerThread(current); // 标记自己持有 return true; } } else if (current == getExclusiveOwnerThread()) { // 自己已经持有(重入) int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); // state +1 return true; } return false; // 拿不到}
3. 三步逐个详解
第 1 步:tryAcquire(CAS 抢锁)
state = 0(没人持锁) ↓CAS(0, 1) → 成功 → 拿锁 ↓ 失败state > 0 且不是自己 → return false ↓进第 2 步
关键:
state是 0 → 锁空闲 → 抢state> 0 → 锁被占 → 检查是不是自己(重入)- 自己持锁 → state + 1(可重入)
- 不是自己 → 返回 false
第 2 步:addWaiter(入队)
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { // CAS 入队 pred.next = node; return node; } } enq(node); // 失败就自旋入队 return node;}
过程:
- 创建 Node(包装当前线程)
- CAS 把自己放到队尾
- 失败就自旋重试
CLH 队列结构:
head (哨兵) → [T1] → [T2] → [T3] → tail ↑ 新节点
第 3 步:acquireQueued(自旋+park)
final boolean acquireQueued(Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { // ← 死循环 final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { // 前驱是 head,再试一次 setHead(node); failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node)) { interrupted |= parkAndCheckInterrupt(); // ← park } } } finally { if (failed) cancelAcquire(node); }}private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) // 前驱说"我会唤醒你" return true; // 可以 park if (ws > 0) { // 前驱取消了 // 跳过所有取消的 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); // 把前驱设为 SIGNAL } return false; // 再自旋几次}
核心逻辑:
1.死循环
2.每次循环先 CAS 抢锁(如果前驱是 head)
3.抢不到 → 检查能否 park
4.前驱是 SIGNAL → park
5.前驱是 0 → CAS 设为 SIGNAL(再自旋几次)
6.被前驱 unpark → 回到 1
4. unlock 的反向流程
// ReentrantLock.unlockpublic void unlock() { sync.release(1);}// AQS.releasepublic final boolean release(int arg) { if (tryRelease(arg)) { // ① 释放(state -1) Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // ② 唤醒后继 return true; } return false;}protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 完全释放 free = true; setExclusiveOwnerThread(null); } setState(c); // state -1 return free;}
关键:
- state -1
- 如果到 0 → 完全释放(清 Owner)
- unpark head 的后继
四、AQS 原理(底层框架)
ReentrantLock 的所有"魔法"都来自 AQS。理解 AQS 就理解了所有阻塞锁。
1. AQS 的 3 大要素
┌────────────────────────────────────────┐│ 1. state (volatile int) ││ - ReentrantLock: 持有次数 ││ - Semaphore: 剩余许可 ││ - CountDownLatch: 倒计数 ││ ││ 2. CLH 双向链表队列 ││ [head] → [T1] → [T2] → [tail] ││ ││ 3. LockSupport.park/unpark ││ - 实际阻塞/唤醒 │└────────────────────────────────────────┘
2. 模板方法模式
AQS 提供流程骨架(已写好): acquire(arg) ↓ tryAcquire(arg) ← 子类实现(5 行) ↓ 失败 addWaiter + acquireQueued ← AQS 实现
子类只需要实现 5 个 abstract 方法:
// 独占模式(ReentrantLock)protected boolean tryAcquire(int arg); // 怎么拿protected boolean tryRelease(int arg); // 怎么放// 共享模式(Semaphore / CountDownLatch)protected int tryAcquireShared(int arg); // 怎么拿protected boolean tryReleaseShared(int arg); // 怎么放// 都用protected boolean isHeldExclusively();
3. 独占 vs 共享
| 模式 | 一次几个线程 | API | 工具 |
|---|---|---|---|
| 独占 | 1 个 | acquire / release | ReentrantLock |
| 共享 | 多个 | acquireShared / releaseShared | Semaphore / CountDownLatch |
4. Node 的 5 个状态
static final int CANCELLED = 1; // 取消static final int SIGNAL = -1; // 后继需要被唤醒(最关键)static final int CONDITION = -2; // 在 Condition 队列static final int PROPAGATE = -3; // 共享模式传播static final int INITIAL = 0; // 初始
关键状态 :SIGNAL(-1)------ 表示"我释放时会唤醒后继"。
5. 公平 vs 非公平的 1 行差异
// 公平锁的 tryAcquireif (c == 0) { if (!hasQueuedPredecessors() && // ← 关键:检查队列里有没有人 compareAndSetState(0, acquires)) { // ... }}// 非公平锁(默认)if (c == 0) { if (compareAndSetState(0, acquires)) { // 直接抢,不检查 // ... }}
差异就这一行 :hasQueuedPredecessors()。
五、综合实战
完整流程图(ReentrantLock.lock 视角)
Thread calls lock.lock() ↓AQS.acquire(1) ↓tryAcquire(1) ── CAS state 0→1 ↓ 失败addWaiter(EXCLUSIVE) ── 入 CLH 队 ↓acquireQueued ↓死循环: ┌─ 前驱是 head 且 tryAcquire 成功? │ ├─ 是 → setHead, return ✓ │ └─ 否 ↓ │ ├─ shouldParkAfterFailedAcquire │ ├─ 前驱是 SIGNAL → 返回 true(可以 park) │ ├─ 前驱是 0 → CAS 设为 SIGNAL,返回 false(再自旋) │ └─ 前驱是 CANCELLED → 跳过 │ └─ parkAndCheckInterrupt └─ LockSupport.park() ← 真正阻塞 ↓ [线程阻塞在这,等前驱 unpark] ↓其他线程 unlock → unparkSuccessor ↓回到死循环顶部
5 个最常问的面试题
Q1:ReentrantLock 和 synchronized 的区别?
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 层面 | JVM | JDK |
| 公平 | ❌ | ✅ 公平/非公平 |
| 可中断 | ❌ | ✅ lockInterruptibly |
| 超时 | ❌ | ✅ tryLock(timeout) |
| 多 Condition | ❌ | ✅ |
| 自动释放 | ✅ | ❌ |
Q2:ReentrantLock 怎么实现可重入?
tryAcquire 里:
if (current == getExclusiveOwnerThread()) { // 自己持锁 int nextc = c + acquires; setState(nextc); // state + 1 return true;}
Q3:CAS 失败后线程去哪了?
进 CLH 队列,在 acquireQueued 里自旋 + park。不是立刻阻塞------先自旋几次,状态机判断后 park。
Q4:AQS 为什么用双向链表?
- 单向不够:取消节点要快速移除(找到前驱)
- 双向:O(1) 删除 + cancelAcquire 简单
Q5:公平锁一定比非公平慢吗?
- 公平:每次 tryAcquire 都要
hasQueuedPredecessors()(多一次读) - 非公平:上来就抢,可能直接抢到(省掉入队 + 自旋)
- 但 :非公平可能导致某些线程饥饿
速记卡片
```
┌─────────────────────────────────────────────┐
│ ReentrantLock + AQS 速记 │
├─────────────────────────────────────────────┤
│ │
│ 【ReentrantLock 怎么用】 │
│ - lock.lock() + try/finally + unlock() │
│ - tryLock() / tryLock(timeout) / lockInterruptibly │
│ - 公平:new ReentrantLock(true) │
│ - Condition:newCondition() + await/signal │
│ │
│ 【加锁原理:3 步骤】 │
│ 1. tryAcquire:CAS state 0→1 │
│ 2. addWaiter:CAS 入队 │
│ 3. acquireQueued:自旋+park │
│ - 前驱是 head → 再试 CAS │
│ - shouldParkAfterFailedAcquire │
│ - SIGNAL → park │
│ - 0 → CAS 设为 SIGNAL │
│ - CANCELLED → 跳过 │
│ │
│ 【AQS 3 要素】 │
│ - state:volatile int,子类赋予含义 │
│ - CLH 双向链表:保存等待线程 │
│ - LockSupport.park/unpark:真正阻塞 │
│ │
│ 【5 个 abstract 方法】 │
│ - tryAcquire / tryRelease │
│ - tryAcquireShared / tryReleaseShared │
│ - isHeldExclusively │
│ │
│ 【Node 5 状态】 │
│ 0 初始 / 1 取消 / -1 SIGNAL ← 最关键 │
│ -2 CONDITION / -3 PROPAGATE │
│ │
│ 【公平 vs 非公平】 │
│ 差异就 1 行:hasQueuedPredecessors() │
│ │
│ 【核心口诀】 │
│ CAS 抢锁 → 失败入队 → 自旋+park │
│ 模板方法:AQS 定流程,子类定细节 │
│ state 多义:ReentrantLock=持有次数 │
│ Semaphore=许可 │
│ CountDownLatch=倒计数 │
│ │
└─────────────────────────────────────────────┘
```