ReentrantLock 与 AQS 完整学习手册

一、学习路径

复制代码
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=倒计数 │

│ │

└─────────────────────────────────────────────┘

```

相关推荐
m0_587383002 小时前
全民健身解决方案软件开发实战:从架构设计到落地指南
java·spring boot·spring·架构·需求分析
白山编程大哥2 小时前
Java OutputStreamWriter 详解:从字符到字节的桥梁
java·开发语言·python
shmily麻瓜小菜鸡3 小时前
JavaScript / TypeScript 易踩坑知识点 —— 异步编程类
开发语言·javascript·typescript
一技安身3 小时前
【信创】Docker‑Compose V2 两种离线部署(独立模式、插件模式)简易教程
java·docker·eureka
七夜zippoe3 小时前
为什么 2026 年每个 Java 团队都该懂 AI Agent
java·开发语言·人工智能
znnnk3 小时前
【Python】GUI 开发从入门到实战(三):PyQt/PySide 进阶之路
开发语言·python·pyqt
时凌云.3 小时前
【2026最新】JDK 下载安装与环境配置全教程(Windows/Mac/Linux 三平台,零基础友好)
java·linux·macos
Despacito10064 小时前
Java后端性能探查工具速查表
java·开发语言
蓝桉柒74 小时前
下载idea,用idea输入一个程序
java·ide·intellij-idea