ReentrantLock 深度剖析(基于Java 21)


一、底层实现原理

整体架构

ReentrantLock 是 JUC 中最核心的显式锁,完全基于 AQS(AbstractQueuedSynchronizer) 构建。其类结构如下:

ReentrantLock
├── implements Lock
└── 内部持有 Sync extends AQS
    ├── NonfairSync extends Sync   (默认,非公平)
    └── FairSync extends Sync      (公平模式)

核心算法:AQS state + CLH 变体队列

┌──────────────────────────────────────────────────────────┐
│  AQS.state 语义(ReentrantLock 的定义)                  │
│                                                          │
│  state == 0   →  锁空闲,无线程持有                     │
│  state == 1   →  锁被某线程持有(第一次获取)            │
│  state == N   →  同一线程重入了 N 次(可重入计数)       │
│                                                          │
│  持有者:exclusiveOwnerThread(AQS 父类字段)            │
└──────────────────────────────────────────────────────────┘

公平 vs 非公平的核心差异

非公平锁(NonfairSync):
  lock() → 先 CAS 尝试抢占(插队)→ 失败才入队等待
  优点:吞吐量高(减少线程切换)
  缺点:可能导致队列中线程长期饥饿

公平锁(FairSync):
  lock() → 检查队列是否有等待者 → 有则直接入队,不插队
  优点:FIFO 顺序,无饥饿
  缺点:线程上下文切换频繁,吞吐量低 20%~30%

关键字段

// AQS 中的核心字段(ReentrantLock 通过继承使用)

// 同步状态:0=无锁,N=重入次数
private volatile int state;

// 当前持锁线程(AbstractOwnableSynchronizer 中定义)
private transient Thread exclusiveOwnerThread;

// CLH 变体等待队列的头节点(dummy 哑节点)
private transient volatile Node head;

// CLH 变体等待队列的尾节点
private transient volatile Node tail;

二、核心数据结构

2.1 AQS 的 CLH 变体等待队列

AQS 使用的不是原始 CLH 队列,而是其变体,区别在于:原始 CLH 是自旋前驱节点的状态,AQS 变体改为挂起线程(park)+ 由前驱唤醒(unpark),避免忙等。

Java 21 的重大变化:队列节点从 Node 改为 AbstractQueuedSynchronizer.Node(内部类重构),但逻辑与 Java 8 一脉相承。

CLH 变体队列结构(双向链表):

         prev              prev              prev
  head ←──── Node-A(Thread1) ←──── Node-B(Thread2) ←──── tail
         ────►              ────►              ────►
          next              next              next

head:dummy 哑节点(不代表实际线程,初始化时创建)
每个 Node 持有:
  - thread      → 被挂起的线程引用
  - waitStatus  → 节点状态(见下表)
  - prev/next   → 双向链接
  - nextWaiter  → Condition 队列链接(独占模式为 null)

waitStatus 状态机:

常量 含义
0 初始值 节点刚入队,尚未设置状态
-1 SIGNAL 后继节点需要被唤醒(最常见)
-2 CONDITION 节点在 Condition 队列中等待
-3 PROPAGATE 共享模式下传播唤醒(ReentrantLock 不用)
1 CANCELLED 线程已超时/中断,节点废弃

2.2 Condition 队列(单向链表)

每个 Condition 对象内部维护一条独立的单向等待队列,与 AQS 主队列分离:

ConditionObject 内部:

  firstWaiter → Node(T1) → Node(T2) → Node(T3) → null
                               ↑
                          lastWaiter

await():  当前节点从 AQS 主队列移到 Condition 队列
signal(): 将 Condition 队列头节点转移回 AQS 主队列

2.3 Java 21 中的节点结构演进

Java 21 对 AQS 内部做了结构性重构(JEP 相关优化),引入了 ExclusiveNodeSharedNode 的区分,但 ReentrantLock 仅用独占模式,核心逻辑不变:

// Java 21 AQS Node(简化)
abstract static class Node {
    volatile Node prev;
    volatile Node next;
    Thread waiter;          // Java 21 改名:原 thread 字段
    volatile int status;    // Java 21 改名:原 waitStatus 字段
}

static final class ExclusiveNode extends Node { }  // 独占模式节点
static final class SharedNode extends Node { }     // 共享模式节点
static final class ConditionNode extends Node      // Condition 节点
    implements ForkJoinPool.ManagedBlocker { ... }

三、源码关键路径

3.1 lock() 路径 —— 非公平锁

// NonfairSync.lock()
final void lock() {
    // ① 第一步:直接 CAS 尝试抢锁(不管队列有无等待者,插队!)
    if (!initialTryLock())
        acquire(1); // CAS 失败 → 走完整 AQS 获取流程
}

// NonfairSync.initialTryLock()
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    // CAS:将 state 从 0 改为 1
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(current); // 记录持锁线程
        return true;
    }
    // CAS 失败,检查是否是重入(当前线程已持锁)
    else if (getExclusiveOwnerThread() == current) {
        int c = getState() + 1;
        if (c < 0) throw new Error("Maximum lock count exceeded"); // 溢出保护
        setState(c); // 重入:state + 1(无需 CAS,持锁线程独占)
        return true;
    }
    return false; // 真正的竞争失败
}
// AQS.acquire(1) —— 完整获取流程
public final void acquire(int arg) {
    // tryAcquire:再试一次(非公平下仍可能插队成功)
    if (!tryAcquire(arg))
        // 失败 → 入队 → 挂起等待
        acquire(null, arg, false, false, false, 0L);
}

// AQS.acquire()(Java 21 重构后的完整版本,含超时/中断支持)
final int acquire(Node node, int arg, boolean shared,
                  boolean interruptible, boolean timed, long time) {
    Thread current = Thread.currentThread();
    byte spins = 0, postSpins = 0;
    boolean interrupted = false, first = false;
    Node pred = null;

    for (;;) {
        // 检查当前节点是否是队列中第一个真实等待者(head 的后继)
        if (!first && (pred = (node == null) ? null : node.prev) != null
                && !(first = (head == pred))) {
            if (pred.status < 0) {         // 前驱已取消
                cleanQueue();              // 清理取消节点
                continue;
            } else if (pred.prev == null) {
                Thread.onSpinWait();       // 前驱是 head,自旋等待
                continue;
            }
        }

        if (first || pred == null) {
            boolean acquired;
            try {
                // 尝试获取锁(非公平再次插队,公平检查队列)
                acquired = tryAcquire(arg);
            } catch (Throwable ex) {
                cancelAcquire(node, interrupted, false);
                throw ex;
            }
            if (acquired) {
                // 成功获取锁:将自己设为新 head
                if (first) {
                    node.prev = null;
                    head = node;
                    pred.next = null;
                    node.waiter = null;
                    if (shared) signalNextIfShared(node);
                    if (interrupted) current.interrupt();
                }
                return 1; // 获取成功
            }
        }

        // 自旋短暂次数后 park 挂起线程
        Node t;
        if ((t = tail) == null) {           // 队列未初始化
            tryInitializeHead();
        } else if (node == null) {           // 创建节点并入队
            node = new ExclusiveNode();
        } else if (pred == null) {           // 将节点加到队尾(CAS)
            node.waiter = current;
            Node p = tail;
            node.setPrevRelaxed(p);
            if (casTail(p, node)) pred = p;
            else node.setPrevRelaxed(null);
        } else if (first && spins != 0) {   // 短暂自旋
            --spins;
            Thread.onSpinWait();
        } else if (node.status == 0) {      // 设置状态为等待被唤醒
            node.status = WAITING;
        } else {
            // park 挂起,等待前驱 unpark 唤醒
            long nanos;
            spins = postSpins = (byte)((postSpins << 1) | 1);
            if (!timed) LockSupport.park(this);
            else if ((nanos = time - System.nanoTime()) > 0L)
                LockSupport.parkNanos(this, nanos);
            else break;
            // 唤醒后检查中断
            node.clearStatus();
            if ((interrupted |= Thread.interrupted()) && interruptible) break;
        }
    }
    return cancelAcquire(node, interrupted, interruptible);
}

3.2 unlock() 路径

// ReentrantLock.unlock()
public void unlock() {
    sync.release(1);
}

// AQS.release(1)
public final boolean release(int arg) {
    if (tryRelease(arg)) { // 尝试释放
        signalNext(head);  // 唤醒队列中下一个等待者
        return true;
    }
    return false;
}

// Sync.tryRelease()
protected final boolean tryRelease(int releases) {
    int c = getState() - releases; // 重入计数减一
    if (getExclusiveOwnerThread() != Thread.currentThread())
        throw new IllegalMonitorStateException(); // 非持锁线程不能释放
    boolean free = (c == 0); // state 降为 0 才是真正释放
    if (free)
        setExclusiveOwnerThread(null); // 清除持锁线程
    setState(c); // 写 volatile state(触发内存屏障!)
    return free;
}

// AQS.signalNext()(Java 21)
private static void signalNext(Node h) {
    Node s;
    if (h != null && (s = h.next) != null && s.status != 0) {
        s.getAndUnsetStatus(WAITING); // CAS 清除 WAITING 状态
        LockSupport.unpark(s.waiter); // 精确唤醒后继线程
    }
}

3.3 Condition.await() / signal() 路径

// ConditionObject.await()
public final void await() throws InterruptedException {
    if (Thread.interrupted()) throw new InterruptedException();

    // ① 将当前线程节点加入 Condition 单向队列
    ConditionNode node = newConditionNode();

    // ② 完全释放锁(state 归零,支持重入),保存重入次数
    int savedState = enableWait(node);

    // ③ park 等待 signal
    LockSupport.setCurrentBlocker(this);
    boolean interrupted = false, cancelled = false, rejected = false;
    while (!canReacquire(node)) { // 未转移回 AQS 主队列则继续等待
        if (interrupted |= Thread.interrupted()) {
            if (cancelled = (node.getAndUnsetStatus(COND) != 0))
                break; // 中断处理
        } else if (node.status != 0) {
            LockSupport.parkNanos(this, 0L);
        } else
            LockSupport.park(this); // 挂起
    }
    LockSupport.setCurrentBlocker(null);

    // ④ 重新获取锁(恢复原重入次数)
    node.clearStatus();
    acquire(node, savedState, false, false, false, 0L);
    if (interrupted) { ... } // 处理中断
}

// ConditionObject.signal()
public final void signal() {
    ConditionNode first = firstWaiter;
    if (!isHeldExclusively()) throw new IllegalMonitorStateException();
    if (first != null)
        doSignal(first, false); // 将 Condition 队列头节点转移到 AQS 主队列
}

// doSignal:将节点从 Condition 队列转移到 AQS 等待队列
private void doSignal(ConditionNode first, boolean all) {
    while (first != null) {
        ConditionNode next = first.nextWaiter;
        if ((firstWaiter = next) == null) lastWaiter = null;
        // CAS 将节点状态从 COND 改为 0,成功则加入 AQS 队列
        if ((first.getAndUnsetStatus(COND) & COND) != 0) {
            enqueue(first); // 加入 AQS 主队列尾部
            if (!all) break;
        }
        first = next;
    }
}

3.4 完整流程图

lock() 调用链:

  ReentrantLock.lock()
       │
       ▼
  NonfairSync.lock()
       │
       ├─ initialTryLock() ──CAS成功──► 持锁成功,返回
       │        │
       │       失败
       │        ▼
       └─ AQS.acquire(1)
               │
               ├─ tryAcquire(1) ──成功──► 持锁成功,返回
               │        │
               │       失败
               │        ▼
               ├─ new ExclusiveNode() 创建节点
               ├─ casTail() 入队
               ├─ 短暂自旋(onSpinWait)
               ├─ node.status = WAITING
               └─ LockSupport.park() 挂起
                        │
                   被 unpark 唤醒
                        │
                        ▼
               tryAcquire() 重新竞争
               成功 → 将自身设为新 head,返回

unlock() 调用链:

  ReentrantLock.unlock()
       │
       ▼
  AQS.release(1)
       │
       ├─ Sync.tryRelease(1)
       │    ├─ state - 1
       │    ├─ state==0 → 清除 ownerThread
       │    └─ setState(volatile写) ←── 内存屏障!
       │
       └─ signalNext(head)
            └─ LockSupport.unpark(后继线程)

四、AQS 关联分析

ReentrantLock 与 AQS 的关系

ReentrantLock 是 AQS 的典型独占模式使用者,关系如下:

  ReentrantLock
      └── Sync(内部类)extends AbstractQueuedSynchronizer
              ├── 实现 tryAcquire(int)    → 定义"如何尝试获取锁"
              ├── 实现 tryRelease(int)    → 定义"如何释放锁"
              └── 使用 AQS 提供的:
                      state 字段         → 锁的重入计数
                      CLH 变体队列       → 等待线程排队
                      park/unpark 框架   → 线程挂起/唤醒

AQS 模板方法模式

// AQS 是模板方法框架,定义好"骨架",子类只需实现"钩子":

// ── AQS 提供的骨架(不可覆盖)─────────────────────────────────
acquire()       → 完整获取流程(入队、挂起、唤醒后重试)
release()       → 完整释放流程(tryRelease + 唤醒后继)
acquireInterruptibly() → 可中断版本
tryAcquireNanos()      → 带超时版本

// ── 子类必须实现的钩子 ──────────────────────────────────────
tryAcquire(int)ReentrantLock 实现:CAS state,记录 ownerThread
tryRelease(int)ReentrantLock 实现:state-1,state==0 则清除 owner

state 字段的完整状态机

        CAS(0→1) 成功
  ┌──── 或重入 setState(n+1)
  │
  │     state=0         state=1         state=N(N>1)
  │    (无锁)─────────►(持锁)────────►(重入 N 次)
  │                      │  ▲               │  ▲
  │                      │  │lock()重入     │  │lock()再重入
  └──────────────────────┘  └───────────────┘
        setState(0)
        (unlock,state-1 降为 0)

CLH 队列如何保证公平性

公平锁 FairSync.tryAcquire() 中的关键判断:

if (!hasQueuedPredecessors()) {
    // 队列为空 或 当前线程就是队列头的后继 → 才允许 CAS 抢锁
    if (compareAndSetState(0, 1)) { ... }
}

hasQueuedPredecessors() 返回 true 的含义:
    队列中有其他线程在当前线程之前等待
    → 当前线程不允许插队,必须排队

五、内存模型 / happens-before

核心保证链路

线程 A(持锁,释放)              线程 B(等待,获取)

  临界区内的写操作
        │
        ▼
  unlock() → tryRelease()
        │
        ▼
  setState(0)        ← volatile 写(写屏障 StoreLoad)
        │
  ────────────────── happens-before ──────────────────
                                        │
                                        ▼
                               tryAcquire() 中
                               compareAndSetState(0,1)
                               ← volatile 读(读屏障 LoadLoad)
                                        │
                                        ▼
                                 临界区内的读操作
                                 一定能看到 A 的写

happens-before 规则对应

规则 在 ReentrantLock 中的体现
volatile 写 → volatile 读 setState(0)(写)happens-before getState()(读)
监视器解锁 → 后续加锁 AQS 通过 volatile state 模拟了等价的效果
unpark → park 返回 unpark(t) happens-before 被唤醒线程的 park() 返回
CAS 成功 → 后续读 compareAndSetState 具有 volatile 读写的联合语义

volatile state 的内存屏障效果

// tryRelease 中:
setState(c);  // volatile 写
// 效果:
//   StoreStore 屏障:前面所有普通写 → 这次 volatile 写(临界区写不重排)
//   StoreLoad  屏障:这次 volatile 写 → 后续任意读(最重的屏障)

// tryAcquire 中:
getState();   // volatile 读
// 效果:
//   LoadLoad  屏障:这次 volatile 读 → 后续普通读(读不重排)
//   LoadStore 屏障:这次 volatile 读 → 后续普通写

六、适用场景

场景一:需要可中断锁的业务

// synchronized 不响应中断,lockInterruptibly() 可以
// 典型:防止死锁、响应超时取消
lock.lockInterruptibly();  // 若被中断则抛 InterruptedException
try { ... } finally { lock.unlock(); }

适用:RPC 调用超时、用户请求取消、批量任务可取消框架

场景二:需要超时获取锁

// 避免无限等待导致线程堆积
if (lock.tryLock(200, TimeUnit.MILLISECONDS)) {
    try { ... } finally { lock.unlock(); }
} else {
    // 超时处理:快速失败、降级、返回错误
    return Result.timeout();
}

适用:高并发接口限流、数据库连接池获取

场景三:多个 Condition 精确唤醒

// synchronized 的 wait/notifyAll 只有一个等待队列
// ReentrantLock 可创建多个 Condition,实现精确唤醒

Lock lock = new ReentrantLock();
Condition notFull  = lock.newCondition(); // 生产者等待队列
Condition notEmpty = lock.newCondition(); // 消费者等待队列

// 生产者
lock.lock(); try {
    while (isFull())  notFull.await();
    enqueue(item);
    notEmpty.signal(); // 只唤醒消费者,不打扰其他生产者
} finally { lock.unlock(); }

适用:有界阻塞队列、流量整形缓冲区(LinkedBlockingQueue 内部即如此实现)

场景四:公平锁防饥饿

// 任务调度系统:保证所有线程平等获得执行机会
ReentrantLock fairLock = new ReentrantLock(true); // 公平模式

适用:公平调度器、资源分配算法、避免某些线程长期得不到执行

场景五:tryLock 无阻塞尝试

// 多资源按顺序 tryLock,失败则全部释放,避免死锁
if (lockA.tryLock()) {
    try {
        if (lockB.tryLock()) {
            try { doWork(); } finally { lockB.unlock(); }
        }
    } finally { lockA.unlock(); }
}

适用:转账等需要同时持有多个锁的场景


七、与同类方案对比

维度 ReentrantLock synchronized StampedLock ReentrantReadWriteLock Semaphore
可重入 ❌(非重入)
公平模式 ✅ 可选 ✅ 可选 ✅ 可选
可中断
超时获取
多 Condition ✅ 任意多个 ❌(只有1个) ✅ 2个
读写分离 ✅ 3种模式 ✅ 读写锁
乐观读 ✅ 无锁读
性能(无竞争) ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐(JVM优化) ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
性能(高竞争) ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐(读多) ⭐⭐⭐⭐
虚拟线程友好 ⚠️(pin问题) ⚠️(pin问题) ✅(CAS主路径) ⚠️
死锁风险 需手动 unlock 自动释放 需手动 unlock 需手动 unlock 需手动 release
适用场景 通用精确控制 简单互斥 读多写少极致性能 读多写少 资源池限流

synchronized vs ReentrantLock 深度对比

synchronized 的优势(Java 21 下):
  ① JVM 自动释放锁(即使异常),不会忘记 unlock
  ② 偏向锁/轻量级锁优化(无竞争下几乎零开销)
  ③ JIT 编译器能对 synchronized 做锁消除、锁粗化优化
  ④ 代码更简洁,不易出错

ReentrantLock 的优势:
  ① lockInterruptibly():响应中断
  ② tryLock(timeout):超时获取
  ③ 多 Condition:精确唤醒
  ④ 公平锁:防止饥饿
  ⑤ lock.getQueueLength():可监控等待队列
  ⑥ lock.isLocked() 等诊断方法

结论:优先 synchronized,当需要上述高级特性时再换 ReentrantLock

八、性能特征与瓶颈

性能模型

无竞争路径(最优):
  CAS(0→1) 成功 → 持锁
  耗时:~10ns(单次 CAS)

轻度竞争(有等待但短暂):
  CAS 失败 → 短暂自旋 → CAS 成功
  耗时:~100ns~1μs

重度竞争(需要挂起):
  入队 → park → unpark → 重新 CAS
  耗时:~10μs~100μs(涉及线程上下文切换)

已知性能瓶颈

瓶颈一:虚拟线程 pin(Java 21 重要问题)

synchronized 块内执行 I/O 或长时间操作 → pin 平台线程
ReentrantLock(基于 AQS park)→ 不 pin,虚拟线程可被卸载

⚠️ 但:ReentrantLock 在某些路径上也可能间接触发 pin
     (如 Condition.await 内部触发 synchronized)

Java 21 推荐:在虚拟线程中用 ReentrantLock 替代 synchronized
              JEP 444 文档明确建议此替换

瓶颈二:公平锁的吞吐量损失

公平锁强制线程切换:
  每次 unlock 必须唤醒队列中的下一个线程(park/unpark)
  即使当前线程可以立即重新获取锁

非公平锁允许"连续持锁":
  A 释放 → A 立即重新获取(无需切换)→ 减少上下文切换
  代价是 B 可能等待更久(饥饿风险)

实测:公平锁吞吐量比非公平锁低 20%~40%(随竞争激烈程度变化)

瓶颈三:虚假唤醒与重试开销

park() 存在虚假唤醒(spurious wakeup)
AQS 通过循环 + 状态检查处理:
  while (!tryAcquire(arg)) { park(); }
  
高竞争下:一次 park/unpark 之后可能再次失败重新排队,
         造成额外的上下文切换开销

优化建议

① 缩短临界区:锁内只放最必要的操作
② 读多写少 → 换 StampedLock 或 ReentrantReadWriteLock
③ 无竞争预期 → 优先 synchronized(JVM 偏向锁)
④ 虚拟线程场景 → 优先 ReentrantLock(避免 pin)
⑤ 监控等待队列长度:lock.getQueueLength() > 阈值 → 告警
⑥ 避免在锁内做 I/O、RPC 等高延迟操作

九、最佳实践

9.1 标准使用模板(不可省略 finally)

// ✅ 标准模板:lock() 和 unlock() 必须配对
ReentrantLock lock = new ReentrantLock();

lock.lock();
try {
    // 临界区
} finally {
    lock.unlock(); // finally 保证一定执行,防止死锁
}

// ❌ 错误:lock 在 try 内部(若 lock() 本身抛异常,unlock 仍会执行)
try {
    lock.lock(); // lock() 极少抛异常,但 lock 应在 try 外
    ...
} finally {
    lock.unlock();
}

9.2 可中断与超时

// 可中断:允许外部取消等待
try {
    lock.lockInterruptibly();
    try { /* 临界区 */ } finally { lock.unlock(); }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复中断标志!
    // 降级处理
}

// 超时:快速失败
if (!lock.tryLock(100, TimeUnit.MILLISECONDS)) {
    // 超时,快速失败或降级
    return;
}
try { /* 临界区 */ } finally { lock.unlock(); }

9.3 条件变量规范用法

// Condition 必须在持锁状态下调用 await/signal
Condition condition = lock.newCondition();

// 等待方:必须用 while,不能用 if(防止虚假唤醒)
lock.lock();
try {
    while (!conditionMet()) { // ✅ while 循环检查
        condition.await();
    }
    // 执行业务
} finally { lock.unlock(); }

// 通知方
lock.lock();
try {
    changeState();
    condition.signal();   // 精确唤醒一个
    // 或 condition.signalAll(); // 唤醒所有
} finally { lock.unlock(); }

9.4 可观测性与监控

ReentrantLock lock = new ReentrantLock();

// 诊断方法(生产监控)
lock.isLocked();              // 是否被某线程持有
lock.isHeldByCurrentThread(); // 是否被当前线程持有
lock.getHoldCount();          // 当前线程重入次数
lock.getQueueLength();        // 等待队列估计长度
lock.hasQueuedThreads();      // 是否有线程在等待
lock.getWaitQueueLength(cond);// 某 Condition 等待线程数

// 推荐:集成 Micrometer 暴露等待队列长度
Gauge.builder("lock.queue.length", lock, ReentrantLock::getQueueLength)
     .tag("name", "myLock")
     .register(meterRegistry);

9.5 重入计数管理(防止重入泄漏)

// ⚠️ 重入了几次,就必须 unlock 几次
lock.lock();          // holdCount = 1
lock.lock();          // holdCount = 2
try {
    ...
    lock.lock();      // holdCount = 3
    try { ... } finally { lock.unlock(); } // holdCount = 2
} finally {
    lock.unlock();    // holdCount = 1
    lock.unlock();    // holdCount = 0 → 真正释放
}

// ⚠️ 常见错误:异常路径少执行了一次 unlock → 锁永远不会释放 → 死锁

十、代码示例(Java 21)

import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.*;

/**
 * ReentrantLock Java 21 综合示例
 * 覆盖:基础用法、超时锁、可中断锁、多Condition、重入、虚拟线程
 */
public class ReentrantLockDemo {

    // ====== 场景一:基础互斥锁 ======
    static class SafeCounter {
        private final ReentrantLock lock = new ReentrantLock();
        private int count = 0;

        public void increment() {
            lock.lock(); // 加锁
            try {
                count++;
            } finally {
                lock.unlock(); // 必须在 finally 中释放
            }
        }

        public int get() {
            lock.lock();
            try { return count; } finally { lock.unlock(); }
        }
    }

    // ====== 场景二:tryLock 超时 + 防死锁 ======
    static void tryLockDemo() throws InterruptedException {
        ReentrantLock lockA = new ReentrantLock();
        ReentrantLock lockB = new ReentrantLock();

        Runnable task = () -> {
            while (true) {
                // 尝试同时获取两把锁,超时则全部释放,避免死锁
                boolean gotA = false, gotB = false;
                try {
                    gotA = lockA.tryLock(50, TimeUnit.MILLISECONDS);
                    gotB = lockB.tryLock(50, TimeUnit.MILLISECONDS);
                    if (gotA && gotB) {
                        System.out.println(Thread.currentThread().getName()
                                + " 同时持有 A 和 B,执行转账");
                        Thread.sleep(10);
                        break; // 成功,退出循环
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                } finally {
                    // 无论是否成功,必须释放已获取的锁
                    if (gotB) lockB.unlock();
                    if (gotA) lockA.unlock();
                }
                // 随机退避,减少活锁
                Thread.sleep(ThreadLocalRandom.current().nextInt(10));
            }
        };

        Thread t1 = new Thread(task, "Thread-A");
        Thread t2 = new Thread(task, "Thread-B");
        t1.start(); t2.start();
        t1.join(); t2.join();
    }

    // ====== 场景三:lockInterruptibly 可中断等待 ======
    static void interruptibleLockDemo() {
        ReentrantLock lock = new ReentrantLock();
        lock.lock(); // 主线程持锁,不释放

        Thread waiter = new Thread(() -> {
            try {
                System.out.println("子线程尝试获锁...");
                lock.lockInterruptibly(); // 可被中断
                try {
                    System.out.println("子线程获锁成功");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                // 被中断后走这里,而非无限等待
                System.out.println("子线程被中断,放弃获锁");
                Thread.currentThread().interrupt(); // 恢复中断标志
            }
        });

        waiter.start();
        try { Thread.sleep(100); } catch (InterruptedException e) { /* ignore */ }
        waiter.interrupt(); // 中断等待中的子线程
        lock.unlock();      // 主线程释放锁
    }

    // ====== 场景四:多 Condition 实现有界队列 ======
    static class BoundedQueue<T> {
        private final Queue<T> queue = new LinkedList<>();
        private final int capacity;
        private final ReentrantLock lock = new ReentrantLock();
        // 两个独立的 Condition:精确唤醒,不会互相干扰
        private final Condition notFull  = lock.newCondition();
        private final Condition notEmpty = lock.newCondition();

        BoundedQueue(int capacity) { this.capacity = capacity; }

        public void put(T item) throws InterruptedException {
            lock.lock();
            try {
                while (queue.size() >= capacity) {
                    notFull.await(); // 队满,生产者等待(必须用 while)
                }
                queue.offer(item);
                notEmpty.signal(); // 通知消费者,精确唤醒,不打扰其他生产者
            } finally { lock.unlock(); }
        }

        public T take() throws InterruptedException {
            lock.lock();
            try {
                while (queue.isEmpty()) {
                    notEmpty.await(); // 队空,消费者等待(必须用 while)
                }
                T item = queue.poll();
                notFull.signal(); // 通知生产者,精确唤醒
                return item;
            } finally { lock.unlock(); }
        }
    }

    // ====== 场景五:可重入演示 ======
    static class ReentrantDemo {
        private final ReentrantLock lock = new ReentrantLock();

        public void outer() {
            lock.lock(); // holdCount = 1
            try {
                System.out.println("outer: holdCount=" + lock.getHoldCount()); // 1
                inner(); // 递归调用,同一线程再次加锁
            } finally {
                lock.unlock(); // holdCount = 0,真正释放
            }
        }

        private void inner() {
            lock.lock(); // holdCount = 2(重入,不死锁)
            try {
                System.out.println("inner: holdCount=" + lock.getHoldCount()); // 2
            } finally {
                lock.unlock(); // holdCount = 1
            }
        }
    }

    // ====== 场景六:Java 21 虚拟线程下的 ReentrantLock ======
    static void virtualThreadDemo() throws InterruptedException {
        // Java 21:虚拟线程中使用 ReentrantLock 不会 pin 平台线程
        // 对比 synchronized:在 synchronized 块内 park 会 pin 平台线程
        ReentrantLock lock = new ReentrantLock();
        int[] counter = {0};
        int taskCount = 10_000;
        CountDownLatch latch = new CountDownLatch(taskCount);

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < taskCount; i++) {
                executor.submit(() -> {
                    lock.lock();
                    try {
                        counter[0]++;
                        // 模拟 I/O(虚拟线程不 pin,平台线程可被复用)
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    } finally {
                        lock.unlock();
                        latch.countDown();
                    }
                });
            }
        }
        latch.await();
        System.out.println("虚拟线程计数结果: " + counter[0]); // 10000
    }

    // ====== 场景七:监控与诊断 ======
    static void monitorDemo() throws InterruptedException {
        ReentrantLock lock = new ReentrantLock(false);
        Condition cond = lock.newCondition();

        Thread waiter = new Thread(() -> {
            lock.lock();
            try { cond.await(1, TimeUnit.SECONDS); }
            catch (InterruptedException e) { Thread.currentThread().interrupt(); }
            finally { lock.unlock(); }
        });
        waiter.start();
        Thread.sleep(50);

        // 诊断
        System.out.println("isLocked:        " + lock.isLocked());
        System.out.println("queueLength:     " + lock.getQueueLength());
        System.out.println("waitQueueLength: " + lock.getWaitQueueLength(cond));
        waiter.join();
    }

    public static void main(String[] args) throws Exception {
        System.out.println("=== 基础计数 ===");
        SafeCounter counter = new SafeCounter();
        var threads = new Thread[10];
        for (int i = 0; i < 10; i++)
            threads[i] = new Thread(() -> { for (int j=0; j<1000; j++) counter.increment(); });
        for (var t : threads) t.start();
        for (var t : threads) t.join();
        System.out.println("count = " + counter.get()); // 10000

        System.out.println("\n=== 可重入 ===");
        new ReentrantDemo().outer();

        System.out.println("\n=== 可中断锁 ===");
        interruptibleLockDemo();

        System.out.println("\n=== tryLock 防死锁 ===");
        tryLockDemo();

        System.out.println("\n=== 多 Condition 有界队列 ===");
        BoundedQueue<Integer> queue = new BoundedQueue<>(3);
        new Thread(() -> { try { for (int i=1; i<=5; i++) { queue.put(i); System.out.println("put: "+i); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();
        new Thread(() -> { try { for (int i=0; i<5; i++) { Thread.sleep(50); System.out.println("take: "+queue.take()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();
        Thread.sleep(500);

        System.out.println("\n=== 监控诊断 ===");
        monitorDemo();

        System.out.println("\n=== Java 21 虚拟线程 ===");
        virtualThreadDemo();
    }
}

十一、面试高频考点

原理类

Q1:ReentrantLock 的可重入如何实现?

tryAcquire 中判断:若 getExclusiveOwnerThread() == currentThread,则 state + 1(无需 CAS,已持锁的线程是唯一写者)。tryReleasestate - 1,只有降为 0 时才清除 ownerThread 并返回 true,触发后继唤醒。

Q2:公平锁和非公平锁底层差异在哪一行代码?

差异在 tryAcquire:非公平锁先 CAS(0,1) 直接抢,成功则持锁(忽略队列);公平锁先调 hasQueuedPredecessors(),若队列中有前驱则直接返回 false(入队等待),不插队。仅这一个判断,带来了完全不同的调度语义。

Q3:AQS 的 CLH 队列为什么是双向链表?

原始 CLH 是单向链表,通过自旋前驱状态来决定是否获取资源,适合多处理器自旋场景。AQS 改为 park/unpark 机制(避免自旋浪费 CPU),需要从后向前找有效前驱(清理 CANCELLED 节点时),因此需要 prev 指针;next 指针用于 release 后精确唤醒后继,所以是双向。

Q4:为什么 unlock() 中 setState() 不需要 CAS?

setState() 是 volatile 写,写语义保证原子性(int 写是原子的)。更重要的是,setState 只在 tryRelease 中调用,而 tryRelease 入口处已经验证了 getExclusiveOwnerThread() == currentThread,即只有持锁线程才能执行此处,不存在并发写竞争,CAS 是多余的。

Q5:Condition.await() 为什么必须在 lock 内调用?

await() 的第一步是"完全释放锁"(savedState = enableWait()),若不持锁则无法释放,IllegalMonitorStateException。另外,Condition 队列的操作(addConditionWaiter)本身不是原子的,必须在锁的保护下执行才安全。这和 Object.wait() 必须在 synchronized 块内调用是完全类似的语义。

对比类

Q6:什么场景下 ReentrantLock 优于 synchronized?

需要以下特性之一时:① 响应中断(lockInterruptibly);② 超时获取(tryLock(time));③ 多个条件变量精确唤醒(newCondition());④ 公平锁(new ReentrantLock(true));⑤ 需要监控等待队列(getQueueLength());⑥ 虚拟线程场景中避免 synchronized 导致的平台线程 pin。

Q7:ReentrantLock 和 StampedLock 如何选择?

ReentrantLock:通用互斥,可重入,API 丰富,适合大多数场景。StampedLock:读多写少的极致性能场景,提供乐观读(无锁),但不可重入、不支持 Condition,使用复杂(需处理 stamp 失效重试)。简单场景用 ReentrantLock,读性能是瓶颈时考虑 StampedLock

场景类

Q8:Condition.await() 为什么要用 while 而不是 if?

存在两种情况需要循环检查:① 虚假唤醒(spurious wakeup):park() 可能在没有被 unpark() 的情况下返回(JVM/OS 规范允许);② 条件竞争signal() 后、当前线程重新获锁前,其他线程可能已经修改了条件使其不再满足。if 只检查一次不够安全,while 重新获锁后再次验证条件才正确。

Q9:一个线程 lock() 了两次但只 unlock() 一次,会发生什么?

state 从 2 减为 1,exclusiveOwnerThread 仍是当前线程,锁没有被释放。其他所有等待该锁的线程将永远阻塞(死锁)。这是 ReentrantLock 相比 synchronized 更危险的地方——synchronized 退出块自动释放,ReentrantLock 需要人工保证重入次数匹配。

Q10:Java 21 中虚拟线程使用 ReentrantLock 和 synchronized 有何区别?

synchronized 在等待时(如 Object.wait())会将虚拟线程 pin(固定) 到平台线程,导致平台线程无法被其他虚拟线程复用,削弱虚拟线程的扩展性优势。ReentrantLock.lock() 获取失败后调用 LockSupport.park(),虚拟线程被挂起并从平台线程卸载,平台线程可以继续运行其他虚拟线程。JEP 444 文档中明确建议:在虚拟线程密集型代码中,用 ReentrantLock 替换 synchronized

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐