2026/9/15 5:36:55

深入解析Java AQS与Condition的底层实现原理

深入解析Java AQS与Condition的底层实现原理 1. AQS与Condition的底层实现解析在Java并发编程领域AbstractQueuedSynchronizerAQS和Condition是构建锁和同步器的基础设施。AQS作为并发包的核心框架其设计精妙程度堪称Java并发编程的内功心法。而Condition接口则提供了类似Object监视器方法的等待/通知机制但与AQS深度集成后展现出更强大的灵活性。提示理解AQS需要先掌握CLH队列的基本概念这是一种基于链表的可扩展、高性能、公平的自旋锁实现方案。AQS内部维护了一个FIFO的等待队列CLH变体通过volatile int类型的state变量表示同步状态。这个看似简单的设计却支撑起了ReentrantLock、CountDownLatch等所有JDK同步器的实现。其核心方法acquire()和release()采用模板方法模式留给子类实现的tryAcquire()和tryRelease()正是各种锁特性差异的来源。1.1 AQS的三大核心组件同步状态state32位的volatile变量不同的同步器对其解释不同。例如ReentrantLock表示持有锁的线程重入次数Semaphore表示可用许可数CountDownLatch表示剩余需要countDown的次数CLH队列节点Node每个等待线程都会被封装成Node节点包含volatile int waitStatus; // CANCELLED(1), SIGNAL(-1), CONDITION(-2), PROPAGATE(-3) volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter; // 用于Condition队列链接条件队列ConditionObject每个Condition实例都维护一个独立的条件队列与同步队列共享节点类型但使用不同的链接方式。这是实现await/signal语义的关键。2. AQS的获取与释放流程剖析2.1 独占模式下的锁获取以ReentrantLock的非公平锁实现为例lock()操作最终会调用AQS的acquire()public final void acquire(int arg) { if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }这个看似简短的方法实际包含四个关键阶段快速尝试获取tryAcquire非公平锁的实现会直接尝试CAS修改stateprotected final boolean tryAcquire(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; }创建节点并入队addWaiter将当前线程包装为EXCLUSIVE模式节点通过CAS快速插入队尾队列中自旋等待acquireQueued在队列中不断尝试获取锁失败后可能进入park状态final boolean acquireQueued(final Node node, int arg) { boolean interrupted false; try { for (;;) { final Node p node.predecessor(); if (p head tryAcquire(arg)) { setHead(node); p.next null; // help GC return interrupted; } if (shouldParkAfterFailedAcquire(p, node)) interrupted | parkAndCheckInterrupt(); } } catch (Throwable t) { cancelAcquire(node); throw t; } }中断补偿selfInterrupt如果在等待过程中被中断重新标记中断状态2.2 锁释放的连锁反应unlock()操作触发的release()过程同样精妙public final boolean release(int arg) { if (tryRelease(arg)) { Node h head; if (h ! null h.waitStatus ! 0) unparkSuccessor(h); return true; } return false; }关键点在于unparkSuccessor()的实现从队尾向前遍历找到离head最近的有效节点避免并发修改导致的问题调用LockSupport.unpark()唤醒该节点关联的线程被唤醒的线程会在acquireQueued()中继续循环尝试获取锁注意head节点在释放时waitStatus为SIGNAL(-1)这是保证释放操作必须唤醒后继节点的关键约定。3. Condition的等待通知机制实现3.1 await()的完整流程当线程调用Condition.await()时实际上经历了以下复杂过程创建CONDITION节点并加入条件队列Node node addConditionWaiter(); // 创建waitStatusCONDITION(-2)的新节点完全释放当前持有的锁int savedState fullyRelease(node); // 完全释放可重入锁的所有计数进入等待状态直到被signal或中断while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode checkInterruptWhileWaiting(node)) ! 0) break; }重新竞争锁if (acquireQueued(node, savedState) interruptMode ! THROW_IE) interruptMode REINTERRUPT;清理被取消的节点if (node.nextWaiter ! null) // clean up if cancelled unlinkCancelledWaiters();处理中断状态if (interruptMode ! 0) reportInterruptAfterWait(interruptMode);3.2 signal()的触发机制signal()操作的核心是将节点从条件队列转移到同步队列public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first firstWaiter; if (first ! null) doSignal(first); } private void doSignal(Node first) { do { if ( (firstWaiter first.nextWaiter) null) lastWaiter null; first.nextWaiter null; } while (!transferForSignal(first) (first firstWaiter) ! null); } final boolean transferForSignal(Node node) { if (!node.compareAndSetWaitStatus(Node.CONDITION, 0)) return false; Node p enq(node); // 将节点加入同步队列 int ws p.waitStatus; if (ws 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL)) LockSupport.unpark(node.thread); // 前驱节点已取消则直接唤醒 return true; }这个过程中有几个关键设计点每次signal()只转移一个节点公平性考虑节点状态从CONDITION变为0后才会加入同步队列如果前驱节点已取消或设置SIGNAL失败会立即唤醒线程4. 实战中的典型问题与优化策略4.1 锁竞争性能优化在高并发场景下AQS的性能表现直接影响系统吞吐量。以下是一些经过验证的优化手段减少临界区范围// 反例 - 整个方法都在锁内 public void process() { lock.lock(); try { // 大量计算和IO操作 } finally { lock.unlock(); } } // 正例 - 只保护必要部分 public void optimizedProcess() { // 前置无竞争操作 lock.lock(); try { // 最小化的临界区 } finally { lock.unlock(); } // 后续无竞争操作 }使用条件队列避免忙等待class BoundedBuffer { final Lock lock new ReentrantLock(); final Condition notFull lock.newCondition(); final Condition notEmpty lock.newCondition(); void put(Object x) throws InterruptedException { lock.lock(); try { while (count items.length) notFull.await(); // 优雅等待而非自旋 // ...入队操作 notEmpty.signal(); } finally { lock.unlock(); } } }4.2 死锁诊断与预防AQS虽然提供了强大的同步能力但使用不当仍会导致死锁。以下是几种典型场景锁顺序死锁// 线程A lockA.lock(); lockB.lock(); // 线程B lockB.lock(); lockA.lock();解决方案定义全局的锁获取顺序所有线程按相同顺序获取锁条件等待死锁Condition cond lock.newCondition(); // 线程A lock.lock(); try { cond.await(); // 释放锁并等待 // 永远等不到signal() } finally { lock.unlock(); }解决方案确保signal()一定会被执行考虑使用带超时的awaitNanos()4.3 监控与调试技巧查看AQS内部状态// 获取同步队列长度 Field tailField AbstractQueuedSynchronizer.class.getDeclaredField(tail); tailField.setAccessible(true); Node tail (Node) tailField.get(lock); // 遍历队列计算长度 int queueLength 0; for (Node p tail; p ! null; p p.prev) { queueLength; }诊断条件队列// 获取ConditionObject内部队列 Field firstWaiterField AbstractQueuedSynchronizer.ConditionObject.class .getDeclaredField(firstWaiter); firstWaiterField.setAccessible(true); Node firstWaiter (Node) firstWaiterField.get(cond);使用jstack分析jstack pid | grep -A10 AbstractQueuedSynchronizer在实际项目中我曾经遇到一个性能问题在高并发下单场景下库存校验的ReentrantLock出现了严重竞争。通过dump线程栈发现有上百个线程阻塞在AQS的acquireQueued()方法上。最终解决方案是将商品库存分片每个商品ID哈希到不同的锁上将锁粒度从全局级别降到商品级别QPS直接提升了15倍。5. AQS的扩展与变种实现5.1 自定义同步器实现基于AQS实现一个简单的二元闭锁class OneShotLatch { private final Sync sync new Sync(); public void signal() { sync.releaseShared(0); } public void await() throws InterruptedException { sync.acquireSharedInterruptibly(0); } private class Sync extends AbstractQueuedSynchronizer { protected int tryAcquireShared(int ignored) { return (getState() 1) ? 1 : -1; } protected boolean tryReleaseShared(int ignored) { setState(1); return true; } } }这个实现展示了AQS的核心扩展方式定义内部Sync类继承AQS重写tryAcquireShared/tryReleaseShared共享模式使用state表示闭锁状态0关闭1打开5.2 性能优化变种MCS锁与CLH不同的另一种队列锁实现特点每个节点维护自己的自旋状态解锁时只需修改后继节点的状态更适合NUMA架构CLH锁变体AQS实际使用的是CLH锁的变体将自旋改为阻塞通过LockSupport.park增加取消机制支持条件变量5.3 不同JDK版本的优化从JDK8到JDK17AQS经历了多次性能优化JDK9优化了取消节点的清理逻辑减少遍历次数JDK15改进了acquireQueued中的中断处理JDK16对ConditionObject的transferAfterCancelledWait进行了优化在最近的一个性能测试中同样的锁竞争场景下JDK17比JDK8有约8%的性能提升主要来自于这些细小的优化累积。