ReentrantLock JDK 17 源码剖析:从 AQS 队列到公平/非公平锁的 3 种加锁路径

发布时间:2026/7/10 5:09:10
ReentrantLock JDK 17 源码剖析:从 AQS 队列到公平/非公平锁的 3 种加锁路径 ReentrantLock JDK 17 源码深度解析AQS 队列与三种加锁路径的全景剖析引言为什么需要深入理解 ReentrantLock在 Java 并发编程领域ReentrantLock作为synchronized关键字的替代方案提供了更灵活的锁控制能力。但真正让开发者困惑的往往不是它的 API 调用而是其底层实现机制。本文将基于 JDK 17 最新源码从 AQS 队列运作机制到三种不同的加锁路径快速 CAS、重入锁、队列等待为你揭示这个并发利器的核心设计思想。与市面上大多数基于 JDK 8 的分析不同JDK 17 中的ReentrantLock在自旋策略、节点状态管理等方面进行了多项优化。理解这些变化不仅能帮助你在面试中脱颖而出更能让你在实际高并发场景中做出更合理的技术选型。1. AQS 架构与 ReentrantLock 的关系1.1 AQS 的双队列模型AbstractQueuedSynchronizerAQS是ReentrantLock的基石它维护了两个核心队列同步队列双向链表结构存储等待获取锁的线程条件队列单向链表存储等待特定条件的线程与Condition接口相关// AQS 中的节点定义JDK 17 精简版 static final class Node { volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 volatile Thread thread; // 关联线程 volatile int waitStatus; // 等待状态 Node nextWaiter; // 条件队列专用 }1.2 ReentrantLock 的类结构JDK 17 中ReentrantLock的继承关系如下ReentrantLock ├── Sync (extends AbstractQueuedSynchronizer) │ ├── NonfairSync │ └── FairSync关键点在于Sync是抽象内部类继承自 AQSNonfairSync实现非公平锁逻辑FairSync实现公平锁逻辑2. 三种加锁路径的源码解析2.1 路径一快速 CAS 抢占非公平锁专属非公平锁在加锁时会首先尝试快速抢占// NonfairSync.lock() final void lock() { if (compareAndSetState(0, 1)) // 无锁状态直接抢占 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }执行流程通过 CAS 尝试将 state 从 0 改为 1成功则设置当前线程为独占线程失败则进入常规获取流程注意JDK 17 优化了compareAndSetState的内存语义使用VarHandle替代了原来的Unsafe操作2.2 路径二重入锁实现当线程已经持有锁时会进入重入逻辑// 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); // state 累加 return true; } return false; }重入特性通过getExclusiveOwnerThread()判断当前线程是否已持有锁state 值记录重入次数必须等所有重入锁释放state0才会真正释放锁2.3 路径三入队等待当锁被其他线程持有时当前线程会进入队列等待// AbstractQueuedSynchronizer.acquire() public final void acquire(int arg) { if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }关键步骤addWaiter()将线程包装为 Node 加入队列尾部acquireQueued()在队列中等待获取锁JDK 17 优化了入队操作的内存屏障使用队列状态流转示例状态值含义CANCELLED1线程已取消SIGNAL-1后继线程需要唤醒CONDITION-2在条件队列中等待PROPAGATE-3共享模式下传播唤醒3. 公平锁 vs 非公平锁的差异3.1 公平锁的核心判断公平锁在尝试获取锁时会检查队列// FairSync.tryAcquire() protected final boolean tryAcquire(int acquires) { final Thread current Thread.currentThread(); int c getState(); if (c 0) { if (!hasQueuedPredecessors() // 关键区别点 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // ... 重入逻辑与非公平锁相同 }3.2 hasQueuedPredecessors() 方法解析这是公平性的核心实现public final boolean hasQueuedPredecessors() { Node h head, t tail; Node s; return h ! t // 队列不为空 ((s h.next) null || s.thread ! Thread.currentThread()); }判断逻辑如果队列为空或当前线程已在队首可以尝试获取锁否则必须排队3.3 性能对比JDK 17 优化指标公平锁非公平锁吞吐量较低高 20%~30%饥饿可能性无可能发生上下文切换较多较少JDK 17 优化减少不必要的唤醒优化 CAS 重试策略4. JDK 17 的重要优化点4.1 自旋策略改进JDK 17 引入了更智能的自旋等待// AbstractQueuedSynchronizer.acquire() if (first spins ! 0) { --spins; Thread.onSpinWait(); // JVM 提示当前在自旋 }优化效果减少不必要的线程挂起在短时间锁持有场景提升性能4.2 内存访问优化使用VarHandle替代Unsafe// AbstractQueuedSynchronizer 中的定义 private static final VarHandle STATE; private static final VarHandle HEAD; private static final VarHandle TAIL; static { try { MethodHandles.Lookup l MethodHandles.privateLookupIn( AbstractQueuedSynchronizer.class, MethodHandles.lookup()); STATE l.findVarHandle(AbstractQueuedSynchronizer.class, state, int.class); // ... 其他初始化 } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } }优势更好的类型安全更精确的内存排序控制4.3 取消节点处理优化JDK 17 改进了取消节点的清理逻辑// 新版 cleanQueue() 实现 private void cleanQueue() { for (;;) { Node h head; if (h null || h.next null) return; // ... 更高效的遍历逻辑 } }5. 实战如何选择锁策略5.1 选择依据场景推荐策略锁持有时间短非公平锁要求严格顺序公平锁高竞争环境非公平锁 自旋优化防止饥饿公平锁5.2 最佳实践示例// 高性能计数器实现 class Counter { private final ReentrantLock lock new ReentrantLock(false); // 非公平锁 private int count; public void increment() { lock.lock(); // 1. 快速路径尝试 try { count; } finally { lock.unlock(); } } // 带超时的安全获取 public boolean safeIncrement(long timeout, TimeUnit unit) throws InterruptedException { if (lock.tryLock(timeout, unit)) { try { count; return true; } finally { lock.unlock(); } } return false; } }6. 常见问题排查技巧6.1 死锁检测使用ThreadMXBean检测ThreadMXBean bean ManagementFactory.getThreadMXBean(); long[] threadIds bean.findDeadlockedThreads(); if (threadIds ! null) { ThreadInfo[] infos bean.getThreadInfo(threadIds); for (ThreadInfo info : infos) { System.out.println(info.getLockName()); } }6.2 锁竞争分析通过 JFR 监控jcmd pid JFR.start duration60s filenamelock.jfr分析事件jdk.JavaMonitorWaitjdk.ThreadPark7. 从源码看设计哲学分层设计AQS 提供骨架子类实现特定语义乐观并发CAS 优先于阻塞渐进式回退从快速路径到完全排队状态压缩用单个state变量管理复杂状态在 JDK 17 的实践中这些设计原则得到了进一步强化特别是在减少内存屏障使用优化热点路径改进取消机制理解这些底层机制才能真正掌握 Java 并发编程的精髓。