Skip to main content

ReentrantLock 源码全解 — 从 tryAcquire 到性能实测

前置阅读:《AQS — Java 并发的基石》。本文假设你已理解 AQS 的 state、CLH 队列、模板方法模式和 ConditionObject 双队列模型。

本文基于 JDK 17 源码,深入分析 ReentrantLock 如何基于 AQS 实现一把功能完整的可重入互斥锁。


目录​

  1. ReentrantLock 的架构
  2. 非公平锁的实现(NonfairSync)
  3. 公平锁的实现(FairSync)
  4. 可重入的实现
  5. tryRelease() — 解锁源码
  6. Condition 在 ReentrantLock 中的应用
  7. 公平 vs 非公平:性能实测
  8. 最佳实践与陷阱
  9. 常见面试题
  10. 总结

1. ReentrantLock 的架构​

打开 ReentrantLock 的源码,你会发现它出奇地简洁——锁的核心逻辑不到 100 行,因为繁重的排队、阻塞、唤醒全部交给了 AQS。

类继承关系​

ReentrantLock implements Lock
│
└── 内部类 Sync extends AbstractQueuedSynchronizer (AQS)
│
├── NonfairSync extends Sync // 非公平锁(默认)
│ └── 重写 tryAcquire()
│
└── FairSync extends Sync // 公平锁
└── 重写 tryAcquire()

构造器​

public ReentrantLock() {
    sync = new NonfairSync();   // 默认非公平
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock 对 state 的定义​

在 AQS 篇中我们知道,state 的语义由子类定义。ReentrantLock 的定义是:

state 值含义
0锁空闲,没有线程持有
1锁被一个线程持有
N(N > 1)锁被同一个线程重入了 N 次

同时,AQS 还有一个字段 exclusiveOwnerThread(继承自 AbstractOwnableSynchronizer),记录当前持有锁的线程。

2. 非公平锁的实现(NonfairSync)​

非公平锁是 ReentrantLock 的默认模式,也是性能更优的模式。

lock() 入口​

// NonfairSync 源码(JDK 17)
static final class NonfairSync extends Sync {

    final void lock() {
        // ① 上来直接 CAS 抢锁(插队!)
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // ② 抢不到,走 AQS 标准流程
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

💡 注意第 ① 步:新线程不管队列里有没有人排队,上来就 CAS 抢锁。这就是"非公平"的含义——插队!

nonfairTryAcquire() — 核心逻辑​

// Sync 类中(NonfairSync 和 FairSync 的父类)
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;  // ✅ 获取成功
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 锁被自己持有 → 重入,state + 1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;  // ✅ 重入成功
    }

    return false;  // ❌ 获取失败,AQS 会将线程放入 CLH 队列
}

完整流程:

线程 X 调用 lock()
│
├── ① CAS(state: 0 → 1) —— 直接插队抢锁
│ ├── 成功 → 设置 owner = X,返回 ✅
│ └── 失败 ↓
│
├── ② acquire(1) → tryAcquire(1) → nonfairTryAcquire(1)
│ │
│ ├── state == 0?→ CAS 再抢一次(第二次机会!)
│ │ ├── 成功 → ✅
│ │ └── 失败 ↓
│ │
│ ├── owner == X?→ 重入,state++ → ✅
│ │
│ └── 都不是 → 返回 false ↓
│
├── ③ addWaiter() → 入队
│
└── ④ acquireQueued() → 排队等待 → park 阻塞 💤

💡 非公平锁给了新来的线程两次插队机会(lock 中一次,tryAcquire 中一次),然后才老老实实排队。


3. 公平锁的实现(FairSync)​

tryAcquire() — 与非公平锁只差一行!​

// FairSync 源码
static final class FairSync extends Sync {

    final void lock() {
        acquire(1);  // 没有 CAS 插队!直接走 AQS 标准流程
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();

        if (c == 0) {
            // ⭐ 关键差异:多了 hasQueuedPredecessors() 检查!
            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;
    }
}

差异对比(只标注不同的行)​

  // 非公平锁
  if (c == 0) {
-     if (compareAndSetState(0, acquires)) {          // 直接 CAS
+     if (!hasQueuedPredecessors() &&                  // 先检查队列
+         compareAndSetState(0, acquires)) {           // 没人排队才 CAS
          setExclusiveOwnerThread(current);
      }
  }

就这一行 hasQueuedPredecessors(),决定了公平与否。

hasQueuedPredecessors() 详解​

// AQS 源码
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t &&                               // 队列非空
        ((s = h.next) == null ||                   // head 的后继为空(有人正在入队)
         s.thread != Thread.currentThread());       // 或者排在最前面的不是自己
}

返回 true 表示"队列中有排在我前面的线程",此时公平锁不允许抢锁,必须排到队尾。

场景:锁刚释放,队列中有 B 在等待

非公平锁:线程 C 新来 → CAS 直接抢 → 可能成功(B 被插队)
公平锁: 线程 C 新来 → hasQueuedPredecessors() = true → 不抢 → 排到 B 后面

4. 可重入的实现​

可重入意味着同一个线程可以多次获取同一把锁,不会死锁。

加锁时:state 递增​

// nonfairTryAcquire / FairSync.tryAcquire 中的重入逻辑
else if (current == getExclusiveOwnerThread()) {
    // 判断当前线程就是锁的持有者
    int nextc = c + acquires;  // state + 1
    if (nextc < 0)
        throw new Error("Maximum lock count exceeded");
    setState(nextc);           // 不需要 CAS!因为只有持有者才能走到这里
    return true;
}

💡 为什么 setState 不需要 CAS? 因为执行到这一步时,当前线程已经是锁的持有者(current == getExclusiveOwnerThread()),不可能有其他线程同时修改 state。这是一个巧妙的优化。

解锁时:state 递减​

// 后面第 5 章会详细讲
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;  // state - 1
    // ...
    if (c == 0) {
        // 重入全部释放,锁真正空闲
        setExclusiveOwnerThread(null);
        free = true;
    }
    setState(c);
    return free;  // 只有 state == 0 时才返回 true
}

重入的状态变化图​

线程 A 第一次 lock(): state: 0 → 1, owner: null → A
线程 A 第二次 lock(): state: 1 → 2, owner: A(不变)
线程 A 第三次 lock(): state: 2 → 3, owner: A(不变)

线程 A 第一次 unlock(): state: 3 → 2, owner: A(不变)
线程 A 第二次 unlock(): state: 2 → 1, owner: A(不变)
线程 A 第三次 unlock(): state: 1 → 0, owner: A → null ← 真正释放!

⚠️ lock 和 unlock 必须配对! 如果 lock 了 3 次只 unlock 了 2 次,锁不会释放,其他线程将永久阻塞。

重入的常见场景​

public class ReentrantDemo {
    private final ReentrantLock lock = new ReentrantLock();

    public void methodA() {
        lock.lock();
        try {
            // 在持有锁的情况下调用 methodB
            methodB();  // ← 这里会再次 lock(),如果不支持重入就死锁了!
        } finally {
            lock.unlock();
        }
    }

    public void methodB() {
        lock.lock();  // 重入:state 1 → 2
        try {
            // 业务逻辑
        } finally {
            lock.unlock();  // state 2 → 1
        }
    }
}

5. tryRelease() — 解锁源码​

// Sync 类源码
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;

    // 只有持有者才能释放锁,否则抛异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();

    boolean free = false;

    if (c == 0) {
        // state 减到 0 → 锁真正被释放
        free = true;
        setExclusiveOwnerThread(null);  // 清除持有者
    }

    setState(c);  // 更新 state(不需要 CAS,原因同重入时的 setState)
    return free;  // 返回 true 时,AQS 会调用 unparkSuccessor 唤醒后继
}

流程串联(结合 AQS 的 release):

// AQS 中的 release(回顾)
public final boolean release(int arg) {
    if (tryRelease(arg)) {       // ReentrantLock 的 tryRelease
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);  // 唤醒 CLH 队列中的下一个线程
        return true;
    }
    return false;  // state 还不为 0(锁被重入了),不唤醒任何人
}
unlock() 调用链:
unlock()
→ sync.release(1)
→ tryRelease(1): state--
├── state == 0 → 清除 owner,返回 true → unparkSuccessor() 唤醒后继
└── state > 0 → 还有重入未释放,返回 false → 不唤醒

6. Condition 在 ReentrantLock 中的应用​

ReentrantLock 通过 newCondition() 返回 AQS 内部类 ConditionObject 的实例。在 AQS 篇中我们已经讲过了 Condition 的双队列模型和源码流程,这里用一个完整的生产者-消费者例子,串联 await/signal 的全链路流转。

完整流转示例​

public class ProducerConsumerTrace {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Queue<Integer> queue = new LinkedList<>();
    private static final int MAX = 3;

    public void produce(int item) throws InterruptedException {
        lock.lock();  // ① 获取锁
        try {
            while (queue.size() == MAX) {
                notFull.await();  // ② 队列满 → 进入 notFull 条件队列
            }
            queue.offer(item);
            System.out.println("生产: " + item);
            notEmpty.signal();  // ③ 通知消费者
        } finally {
            lock.unlock();  // ④ 释放锁
        }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();  // 队列空 → 进入 notEmpty 条件队列
            }
            int item = queue.poll();
            System.out.println("消费: " + item);
            notFull.signal();  // 通知生产者
            return item;
        } finally {
            lock.unlock();
        }
    }
}

全链路流转图​

假设队列已满(size == 3),生产者 P 调用 produce():

【阶段一:P 进入条件等待】

P 调用 lock.lock()
→ state: 0 → 1, owner: P
→ 同步队列: [head]

P 发现 queue 满了,调用 notFull.await()
→ ① addConditionWaiter(): P 加入 notFull 条件队列
→ ② fullyRelease(): state: 1 → 0, owner: null
→ 唤醒同步队列后继(如有)
→ ③ LockSupport.park(P) → 💤

此时状态:
同步队列: [head](空)
notFull 条件队列: [P, ws=-2]
notEmpty 条件队列: (空)
锁: 空闲 (state=0)

──────────────────────────────────────────

【阶段二:C 消费并唤醒 P】

消费者 C 调用 lock.lock()
→ state: 0 → 1, owner: C

C 消费一个元素,调用 notFull.signal()
→ transferForSignal(P):
→ P 的 ws: -2 → 0
→ enq(P): P 加入同步队列尾部

此时状态:
同步队列: [head] ←→ [P]
notFull 条件队列: (空)
锁: C 持有 (state=1)

C 调用 lock.unlock()
→ state: 1 → 0, owner: null
→ unparkSuccessor(head) → unpark(P)

──────────────────────────────────────────

【阶段三:P 被唤醒,重新获取锁】

P 从 park 中醒来
→ isOnSyncQueue(P) = true → 退出 while 循环
→ acquireQueued(P, savedState=1)
→ P 的前驱是 head → tryAcquire(1) → 成功!
→ state: 0 → 1, owner: P
→ P 成为新 head

P 继续执行 await() 之后的代码
→ queue.offer(item) → 生产成功
→ notEmpty.signal() → 唤醒消费者
→ lock.unlock()

💡 关键理解:Condition 的 await/signal 本质上是在两个队列之间转移 Node:

  • await():同步队列 → 条件队列(同时释放锁)
  • signal():条件队列 → 同步队列(等待重新获取锁)

7. 公平 vs 非公平:性能实测​

Benchmark 代码​

以下是一个可直接运行的性能对比测试,你可以在自己的机器上运行:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 公平锁 vs 非公平锁性能对比
 * 运行方式:javac LockBenchmark.java && java LockBenchmark
 */
public class LockBenchmark {

    private static final int THREAD_COUNT = 20;
    private static final int OPERATIONS_PER_THREAD = 100_000;

    public static void main(String[] args) throws Exception {
        // 预热
        System.out.println("预热中...");
        runBenchmark(new ReentrantLock(false), "预热");

        System.out.println("\n===== 正式测试 =====\n");

        // 非公平锁
        long unfairTime = runBenchmark(new ReentrantLock(false), "非公平锁");

        // 公平锁
        long fairTime = runBenchmark(new ReentrantLock(true), "公平锁");

        // 对比
        System.out.println("\n===== 结果对比 =====");
        System.out.printf("非公平锁: %,d ms%n", unfairTime);
        System.out.printf("公  平锁: %,d ms%n", fairTime);
        System.out.printf("公平锁是非公平锁的 %.1f 倍%n", (double) fairTime / unfairTime);
    }

    private static long runBenchmark(ReentrantLock lock, String name) throws Exception {
        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch doneLatch = new CountDownLatch(THREAD_COUNT);

        // 共享计数器
        long[] counter = {0};

        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                try {
                    startLatch.await(); // 等待发令
                } catch (InterruptedException e) {
                    return;
                }

                for (int j = 0; j < OPERATIONS_PER_THREAD; j++) {
                    lock.lock();
                    try {
                        counter[0]++;
                    } finally {
                        lock.unlock();
                    }
                }
                doneLatch.countDown();
            }).start();
        }

        long start = System.currentTimeMillis();
        startLatch.countDown(); // 发令!
        doneLatch.await();
        long elapsed = System.currentTimeMillis() - start;

        System.out.printf("[%s] 耗时: %,d ms, 计数: %,d%n", name, elapsed, counter[0]);
        return elapsed;
    }
}

预期结果​

在多数机器上(8 核+),你会看到类似的结果:

===== 结果对比 =====
非公平锁: 312 ms
公 平锁: 1,847 ms
公平锁是非公平锁的 5.9 倍

实际倍数取决于核心数和竞争强度,通常在 2-10 倍之间。

为什么非公平锁更快?​

非公平锁的优势场景:

线程 A 持有锁 → 释放锁 → unparkSuccessor(B)
│
│ 此时 B 正在被唤醒(上下文切换中...)
│
线程 C 新来 → CAS 抢锁 → 成功!→ 执行临界区 → 释放锁
│
│ B 终于醒来 → tryAcquire → 成功
│
效果:C 在 B 苏醒的间隙中完成了工作,锁没有空转!

非公平锁通过允许"插队",避免了锁在线程上下文切换期间的空闲浪费,提高了整体吞吐量。

什么时候必须用公平锁?​

  • 防止线程饥饿:如果锁竞争非常激烈,且线程优先级差异大,非公平锁可能导致某些线程长时间拿不到锁
  • 严格的顺序要求:业务上需要按请求顺序处理
  • 长时间持有锁的场景:锁持有时间远大于上下文切换时间时,非公平的性能优势减弱

💡 经验法则:99% 的场景用默认的非公平锁。只有明确出现线程饥饿时,才考虑公平锁。


8. 最佳实践与陷阱​

8.1 lock() 必须写在 try 外面​

// ✅ 正确
lock.lock();
try {
    // 业务代码
} finally {
    lock.unlock();
}

// ❌ 错误
try {
    lock.lock();   // 如果 lock() 之前出现异常(如 lock 为 null)
    // 业务代码
} finally {
    lock.unlock(); // finally 中 unlock 一个未持有的锁 → IllegalMonitorStateException
}

深层原因:如果 lock() 写在 try 内,而 lock() 执行前发生了异常(比如 lock 对象本身为 null 引发 NPE),finally 中的 unlock() 会尝试释放一个没有获取过的锁,导致 IllegalMonitorStateException,且会掩盖原始异常。

8.2 tryLock 防死锁模式​

public void transferMoney(Account from, Account to, int amount) {
    while (true) {
        if (from.lock.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (to.lock.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        // 两把锁都拿到了,执行转账
                        from.balance -= amount;
                        to.balance += amount;
                        return;
                    } finally {
                        to.lock.unlock();
                    }
                }
            } finally {
                from.lock.unlock();
            }
        }
        // 没拿到 → 随机等待后重试,避免活锁
        Thread.sleep((long) (Math.random() * 100));
    }
}

8.3 lockInterruptibly 响应中断​

// 适用场景:用户取消操作时,不希望线程一直阻塞等锁
public void cancelableTask() {
    try {
        lock.lockInterruptibly();  // 等锁期间可被中断
        try {
            // 业务代码
        } finally {
            lock.unlock();
        }
    } catch (InterruptedException e) {
        // 被中断了,做清理工作
        Thread.currentThread().interrupt();
        System.out.println("任务被取消");
    }
}

8.4 synchronized vs ReentrantLock 最终选型​

场景推荐理由
简单的互斥同步synchronized简洁安全,JVM 自动释放,不会忘记 unlock
需要 tryLock / 超时ReentrantLocksynchronized 不支持
需要可中断ReentrantLocklockInterruptibly()
需要公平锁ReentrantLocksynchronized 是非公平的
需要多个 ConditionReentrantLocksynchronized 只有一个等待队列
需要在 finally 中确保释放都行synchronized 自动释放更安全

9. 常见面试题​

Q1:ReentrantLock 和 synchronized 的区别?​

从实现层面回答:

维度synchronizedReentrantLock
实现层面JVM 指令(monitorenter/monitorexit)Java API(基于 AQS)
锁获取自动手动 lock()
锁释放自动(异常也会释放)手动 unlock()(必须 finally)
可重入✅✅
底层机制对象头 Mark Word + MonitorAQS 的 state + CLH 队列
优化偏向锁 → 轻量级锁 → 重量级锁CAS 抢锁 → park 阻塞

Q2:AQS 为什么用 CLH 队列而不是普通队列?​

CLH 队列的优势:

  1. 无锁入队:通过 CAS 操作 tail 指针实现入队,不需要额外加锁
  2. 局部自旋:每个节点只关注前驱的状态,减少了全局竞争
  3. 公平性好:天然 FIFO 顺序,适合实现公平锁
  4. 取消方便:双向链表支持 O(1) 的节点删除

Q3:为什么非公平锁比公平锁性能好?​

一句话:非公平锁在锁释放和下一个线程被唤醒之间的"空窗期",允许新来的线程直接获取锁,避免了锁空转。

本质:线程上下文切换(~10μs)比 CAS(~10ns)慢 1000 倍。非公平锁用 CAS 抢锁替代了部分上下文切换。

Q4:ReentrantLock 能防止死锁吗?​

ReentrantLock 本身不能自动检测死锁,但它提供了预防死锁的工具:

// 方法1:tryLock 超时
if (lock.tryLock(1, TimeUnit.SECONDS)) { ... }

// 方法2:lockInterruptibly 响应中断
lock.lockInterruptibly();

而 synchronized 一旦阻塞就无法退出,除非获取到锁或线程被终止。

Q5:公平锁和非公平锁的代码差异在哪?​

两处差异:

  1. lock() 方法:非公平锁先 CAS 插队,公平锁直接 acquire(1)
  2. tryAcquire() 方法:公平锁多了 !hasQueuedPredecessors() 检查

本质上就是:公平锁在每次获取前检查"前面有没有人排队"。


10. 总结​

ReentrantLock 全貌​

ReentrantLock
│
├── Sync (extends AQS)
│ ├── state: 0=空闲, ≥1=持有(重入次数)
│ ├── exclusiveOwnerThread: 持有锁的线程
│ ├── tryRelease(): state-- → 到 0 才真正释放
│ └── newCondition() → ConditionObject(双队列模型)
│
├── NonfairSync
│ ├── lock(): CAS 插队 → acquire(1)
│ └── tryAcquire(): 不检查队列,直接 CAS
│
└── FairSync
├── lock(): acquire(1)(不插队)
└── tryAcquire(): hasQueuedPredecessors() + CAS

核心结论​

要点说明
ReentrantLock ≈ AQS + 100 行代码排队、阻塞、唤醒全部复用 AQS
公平 vs 非公平差在一行hasQueuedPredecessors()
可重入 = state 的递增递减state > 0 时 unlock 不会释放锁
Condition = 双队列转移await 进条件队列,signal 回同步队列
默认用非公平,性能好 2-10 倍除非明确需要防饥饿

系列文章导航​

已完成:
📄 Java 并发工具类全景指南 → java-concurrency-tools.md
📄 AQS — Java 并发的基石 → aqs-deep-dive.md
📄 ReentrantLock 源码全解(本文) → reentrantlock-source-code.md

可选后续:
📝 Semaphore 源码解析 — AQS 共享模式的典型应用
📝 ReentrantReadWriteLock 源码解析 — 读写锁的 state 拆分设计
📝 Java 内存模型(JMM)与 happens-before

📝 作者注:本文基于 JDK 17 源码。核心设计自 JDK 5 以来保持稳定,不同版本的差异主要在性能优化和代码组织上,不影响理解。