1. ReentrantLock与AQS的关系解析
ReentrantLock作为Java并发包中的核心同步工具,其底层实现完全依赖于AbstractQueuedSynchronizer(AQS)框架。这种设计模式体现了Java并发大师Doug Lea"不要重复造轮子"的设计哲学——通过AQS这个同步器基础框架,开发者可以专注于实现特定同步语义,而不用处理复杂的线程排队、阻塞/唤醒等底层机制。
关键理解:AQS本质上是一个"同步模板",它定义了获取/释放资源的框架方法,而具体资源获取/释放的逻辑则由子类实现。这种模板方法模式使得ReentrantLock可以专注于实现可重入锁的语义。
在AQS框架中,最重要的两个概念是:
- 同步状态(state):一个volatile修饰的int变量,表示锁的持有情况
- 等待队列:一个CLH队列的变体,用于管理等待线程
ReentrantLock通过继承AQS并实现其tryAcquire/tryRelease等钩子方法,将AQS的通用同步机制与可重入锁的特定语义相结合。这种设计使得ReentrantLock的代码量保持在300行左右(包括公平/非公平实现),却能够提供完整的互斥锁功能。
2. 核心源码实现剖析
2.1 锁的获取流程
非公平锁的实现(默认模式)的获取逻辑体现在Sync类的nonfairTryAcquire方法中:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }这段代码揭示了三个关键设计:
- CAS操作实现无锁化竞争:当state为0时,通过compareAndSetState原子操作尝试获取锁
- 重入计数机制:通过state累加值记录重入次数
- 线程检查:通过exclusiveOwnerThread确认是否是当前持有线程
公平锁的实现差异仅在于多了一个hasQueuedPredecessors()检查,确保没有更早的等待线程才会尝试获取锁。
2.2 锁的释放流程
释放操作的实现相对简单,主要体现在tryRelease方法中:
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); return free; }释放逻辑的关键点:
- 线程安全检查:确保只有锁的持有者才能释放
- 重入计数递减:每次释放减少state值
- 完全释放判断:当state归零时清除持有线程标记
2.3 AQS队列管理机制
当获取锁失败时,线程会被加入等待队列。AQS的队列管理有几个精妙设计:
节点状态(waitStatus):
- SIGNAL(-1):表示后继节点需要被唤醒
- CANCELLED(1):表示线程已取消
- CONDITION(-2):表示节点在条件队列中
入队操作:
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)) { pred.next = node; return node; } } enq(node); return node; }出队唤醒: 通过unparkSuccessor方法唤醒后继节点,但会跳过已取消的节点
3. 关键设计模式解析
3.1 模板方法模式的应用
AQS定义了获取/释放资源的骨架,而将具体操作留给子类实现。这种模板方法模式在ReentrantLock中体现为:
// AQS中的模板方法 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 子类需要实现的钩子方法 protected boolean tryAcquire(int arg);这种设计使得:
- AQS负责统一的线程排队、阻塞/唤醒机制
- ReentrantLock只需关注可重入锁的特定语义
3.2 状态模式的应用
ReentrantLock通过内部类Sync、NonfairSync和FairSync实现了锁的不同行为模式:
// 基础同步控制 abstract static class Sync extends AbstractQueuedSynchronizer { //... } // 非公平锁实现 static final class NonfairSync extends Sync { //... } // 公平锁实现 static final class FairSync extends Sync { //... }这种设计允许在运行时选择不同的锁策略,而不需要修改客户端代码。
4. 性能优化技巧
4.1 自旋优化
在acquireQueued方法中,AQS使用了有限次数的自旋尝试:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }这种设计减少了线程挂起/唤醒的开销,特别是在锁持有时间较短的场景。
4.2 取消节点清理
AQS在取消节点时会执行清理操作,避免队列中出现"僵尸"节点:
private void cancelAcquire(Node node) { if (node == null) return; node.thread = null; Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; Node predNext = pred.next; node.waitStatus = Node.CANCELLED; if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // ... } }这种清理机制保证了队列的健康状态,避免了无效节点的累积。
5. 常见问题排查
5.1 死锁场景分析
虽然ReentrantLock本身不会导致死锁,但不正确的使用方式仍然可能引发问题:
- 典型错误案例:
lock.lock(); try { // 业务代码 lock.lock(); // 忘记解锁导致死锁 try { // 更多业务代码 } finally { // 忘记解锁 } } finally { lock.unlock(); }- 排查建议:
- 使用jstack查看线程状态
- 确保lock/unlock成对出现
- 考虑使用tryLock设置超时
5.2 性能问题排查
在高并发场景下,ReentrantLock可能出现性能瓶颈:
- 常见症状:
- CPU使用率高但吞吐量低
- 大量线程处于BLOCKED状态
- 优化方向:
- 评估是否真的需要公平锁(非公平锁吞吐量通常高30%)
- 考虑减小临界区范围
- 评估是否可以使用读写锁(ReentrantReadWriteLock)
6. 最佳实践建议
- 锁使用规范:
Lock lock = new ReentrantLock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); }- 高级特性利用:
- 可中断获取:lockInterruptibly()
- 尝试获取:tryLock()
- 条件变量:newCondition()
- 监控建议:
- 通过getQueueLength()监控等待线程数
- 通过isHeldByCurrentThread()调试锁持有情况
在实际项目中,我发现很多开发者没有充分利用ReentrantLock提供的丰富功能。比如条件变量(Condition)可以更精细地控制线程通信,而tryLock结合超时参数可以构建更健壮的分布式系统降级方案。理解AQS的实现机制不仅有助于正确使用ReentrantLock,也为实现自定义同步器提供了坚实基础。