前言在Java并发编程中ReentrantLock是一个非常重要的可重入互斥锁它比synchronized提供了更灵活的锁机制支持公平/非公平模式、可响应中断、超时等待、多条件变量等。但真正让ReentrantLock强大的是其底层依赖的AbstractQueuedSynchronizerAQS框架。AQS是JUCjava.util.concurrent包的基石CountDownLatch、Semaphore、ReentrantReadWriteLock等同步组件都基于它构建。本文将逐行解析源码彻底讲清楚AQS的核心数据结构state、CLH队列、Node非公平锁的完整加锁/解锁流程公平锁的实现原理及与非公平锁的区别同步队列的入队、阻塞、唤醒机制重入锁的实现原理一、AQS核心架构1.1 AQS是什么AbstractQueuedSynchronizer是一个抽象类它提供了一个FIFO队列来管理等待锁的线程并定义了一个int类型的state作为同步状态。子类只需要实现tryAcquire、tryRelease等钩子方法即可构造出自己的同步器。1.2 核心属性// AbstractQueuedSynchronizer 中的核心属性 // 同步状态volatile保证可见性 // 在ReentrantLock中state0表示锁空闲state0表示锁被持有值表示重入次数 private volatile int state; // 同步队列的头指针懒加载初始化时为空 private transient volatile Node head; // 同步队列的尾指针 private transient volatile Node tail;// AbstractOwnableSynchronizerAQS的父类中的属性 // 记录当前持有锁的线程独占模式下使用 private transient Thread exclusiveOwnerThread;1.3 Node节点结构同步队列中的每个节点都是一个Node对象static final class Node { // 节点等待模式 static final Node SHARED new Node(); // 共享模式 static final Node EXCLUSIVE null; // 独占模式 // 等待状态常量 static final int CANCELLED 1; // 线程已取消等待 static final int SIGNAL -1; // 当前节点释放锁后需要唤醒后继节点 static final int CONDITION -2; // 在条件队列中等待 static final int PROPAGATE -3; // 共享模式下需要传播唤醒 // 节点状态重要-1 SIGNAL1 CANCELLED0 初始状态 volatile int waitStatus; // 双向链表指针 volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 // 该节点封装的线程 volatile Thread thread; // 指向条件队列的下一个节点Condition相关本文暂不详述 Node nextWaiter; }1.4 同步队列结构图重要特性队列是FIFO先进先出的头节点是一个哨兵节点一般不关联具体的线程或关联当前持有锁的线程每个节点封装一个等待的线程节点状态SIGNAL表示前驱节点释放锁后会唤醒我二、非公平锁完整源码解析2.1 加锁入口lock()// ReentrantLock 构造方法 public ReentrantLock(boolean fair) { sync fair ? new FairSync() : new NonfairSync(); } // 调用 lock() 时实际执行的是 sync.lock() public void lock() { sync.lock(); }NonfairSync.lock()实现final void lock() { // 【第一次插队机会】快速CAS尝试获取锁 // 如果锁空闲state0则直接抢锁成功 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 抢锁失败进入完整获取流程 }为什么叫非公平新来的线程可以不排队直接尝试抢锁。如果抢成功了队列中等待的线程只能继续等。2.2 acquireAQS模板方法public final void acquire(int arg) { // tryAcquire尝试获取锁包含重入逻辑 // addWaiter获取失败将当前线程封装成节点加入队列 // acquireQueued在队列中自旋等待 if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); // 如果等待过程中被中断过补上中断标记 }2.3 tryAcquire尝试获取锁AQS中的钩子方法protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }NonfairSync中的实现protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }核心逻辑 nonfairTryAcquirefinal boolean nonfairTryAcquire(int acquires) { final Thread current Thread.currentThread(); int c getState(); // 【情况1】锁空闲 if (c 0) { // 【第二次插队机会】CAS获取锁 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 【情况2】锁已被当前线程持有 → 重入 else if (current getExclusiveOwnerThread()) { int nextc c acquires; if (nextc 0) // 溢出保护 throw new Error(Maximum lock count exceeded); setState(nextc); // 不需要CAS因为只有当前线程才能执行到这里 return true; } // 【情况3】锁被其他线程持有 return false; }重入锁的关键同一个线程可以多次获取同一把锁每次重入state加1释放时需要释放相同次数。2.4 addWaiter入队操作private Node addWaiter(Node mode) { // 创建新节点封装当前线程modenull表示独占模式 Node node new Node(Thread.currentThread(), mode); // 【快速尝试】如果队列已存在尝试直接CAS追加到队尾 Node pred tail; if (pred ! null) { node.prev pred; if (compareAndSetTail(pred, node)) { pred.next node; return node; } } // 【兜底方案】队列不存在或CAS失败进入自旋入队 enq(node); return node; }2.5 enq自旋入队线程安全private Node enq(final Node node) { for (;;) { // 自旋直到入队成功 Node t tail; if (t null) { // 队列尚未初始化 // 创建哨兵节点作为头节点 if (compareAndSetHead(new Node())) tail head; // 头尾指向同一个哨兵节点 } else { // 标准入队操作 node.prev t; if (compareAndSetTail(t, node)) { t.next node; return t; // 返回前驱节点 } } } }为什么要创建哨兵节点有了哨兵节点队列永远不会为空简化了唤醒逻辑——每次从头节点的后继开始唤醒即可。2.6 acquireQueued排队等待这是最核心的等待逻辑final boolean acquireQueued(final Node node, int arg) { boolean failed true; try { boolean interrupted false; for (;;) { final Node p node.predecessor(); // 获取前驱节点 // 【关键判断1】前驱是头节点 → 说明当前节点是队列中等待最久的 if (p head tryAcquire(arg)) { // 获取锁成功将当前节点设置为新头节点 setHead(node); // 头节点会清空thread引用 p.next null; // help GC failed false; return interrupted; // 正常退出 } // 【关键判断2】获取锁失败判断是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt()) interrupted true; // 记录中断状态 } } finally { if (failed) cancelAcquire(node); // 异常情况取消获取 } }核心理解只有前驱是头节点的节点才有资格尝试获取锁保证FIFO获取失败时会阻塞自己等待前驱节点唤醒2.7 shouldParkAfterFailedAcquire决定是否阻塞private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws pred.waitStatus; // 【情况1】前驱状态已经是SIGNAL if (ws Node.SIGNAL) // 放心阻塞前驱释放锁时会唤醒我 return true; // 【情况2】前驱已取消等待 if (ws 0) { // 跳过所有取消的节点向前找到第一个有效节点 do { node.prev pred pred.prev; } while (pred.waitStatus 0); pred.next node; } // 【情况3】前驱状态为0或PROPAGATE else { // 将前驱状态设置为SIGNAL // 注意这里只是设置状态返回false外层会再尝试一次获取锁 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; // 当前不需要阻塞外层会再循环一次 }为什么设置SIGNAL后返回false因为设置完成后前驱节点可能刚好释放了锁所以应该再给当前节点一次获取锁的机会。2.8 parkAndCheckInterrupt阻塞线程private final boolean parkAndCheckInterrupt() { LockSupport.park(this); // 阻塞当前线程 return Thread.interrupted(); // 唤醒后返回中断状态并清除中断标记 }LockSupport.park()会让线程进入WAITING状态直到被unpark()唤醒。2.9 加锁完整流程图┌─────────────────────────────────────┐ │ lock() 调用 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ 快速CAS抢锁 (state0→1)? │ └─────────────────┬───────────────────┘ 成功 │ │ 失败 ▼ ▼ ┌─────────────────┐ ┌─────────────────────────┐ │ 获取锁成功 │ │ tryAcquire尝试获取锁 │ │ 设置owner线程 │ │ (含重入逻辑) │ └─────────────────┘ └───────────┬─────────────┘ ▼ 成功 │ │ 失败 ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ 获取锁成功 │ │ addWaiter │ └─────────────────┘ │ 入队 │ └────────┬────────┘ ▼ ┌─────────────────┐ │ acquireQueued │ │ 自旋等待 │ └────────┬────────┘ ▼ ┌─────────────────┐ │ 前驱是头节点? │ └────────┬────────┘ 是 │ 否 ▼ ┌─────────────────┐ │ tryAcquire成功? │ └────────┬────────┘ 是 │ 否 ▼ ┌─────────────────┐ │ 设置新头节点 │ │ 返回 │ └─────────────────┘ │ 否 ▼ ┌─────────────────┐ │ shouldPark... │ │ 确保前驱SIGNAL │ └────────┬────────┘ ▼ ┌─────────────────┐ │ parkAndCheck... │ │ 阻塞线程 │ └────────┬────────┘ │ (唤醒后重新自旋) ◄──────────────┘三、解锁流程完整解析3.1 unlock入口public void unlock() { sync.release(1); }3.2 releaseAQS释放模板public final boolean release(int arg) { if (tryRelease(arg)) { // 尝试释放锁 Node h head; // 头节点不为空且状态不为0说明有需要唤醒的后继节点 if (h ! null h.waitStatus ! 0) unparkSuccessor(h); // 唤醒后继节点 return true; } return false; }3.3 tryRelease释放锁重入减1protected 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); // 更新state不需要CAS因为只有当前线程能执行这里 return free; }关键点重入锁的释放是分层的。每次unlock()只减少一次计数只有计数归零时锁才真正释放。3.4 unparkSuccessor唤醒后继线程private void unparkSuccessor(Node node) { int ws node.waitStatus; if (ws 0) compareAndSetWaitStatus(node, ws, 0); // 清除SIGNAL状态 Node s node.next; // 如果后继节点为空或已取消从尾部向前找第一个有效节点 if (s null || s.waitStatus 0) { s null; // 为什么要从后往前因为并发环境下next指针可能不完整但prev是可靠的 for (Node t tail; t ! null t ! node; t t.prev) if (t.waitStatus 0) s t; } if (s ! null) LockSupport.unpark(s.thread); // 唤醒线程 }3.5 解锁流程图┌─────────────────────────────────────┐ │ unlock() 调用 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ tryRelease: state减1 │ │ 判断是否完全释放 (c 0)? │ └─────────────────┬───────────────────┘ 否 │ │ 是 ▼ ▼ ┌─────────────────┐ ┌─────────────────────────┐ │ 返回false │ │ 清空owner线程 │ │ 锁仍被持有 │ │ 返回true │ └─────────────────┘ └───────────┬─────────────┘ ▼ ┌─────────────────────────────┐ │ head ! null │ │ head.waitStatus ! 0? │ └───────────┬─────────────────┘ 是 │ 否 ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ unparkSuccessor │ │ 直接返回 │ │ 唤醒后继节点 │ └─────────────────┘ └─────────────────┘四、公平锁实现原理4.1 公平锁与非公平锁的核心区别公平锁与非公平锁的唯一区别在于尝试获取锁时是否检查队列中已有等待线程。特性非公平锁公平锁新线程能否插队能两次插队机会不能队列中等待线程的优先级无特殊保护等待最久的线程优先吞吐量高低可能产生饥饿是否默认选择是否4.2 公平锁的tryAcquire实现// FairSync 中的 tryAcquire 方法 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; }4.3 hasQueuedPredecessors检查队列中是否有等待线程public final boolean hasQueuedPredecessors() { Node t tail; Node h head; Node s; // 条件为真的情况队列不为空且头节点的后继节点不是当前线程 return h ! t // 队列中至少有2个节点头节点 至少一个等待节点 ((s h.next) null || // 头节点的后继为空极端情况 s.thread ! Thread.currentThread()); // 后继节点的线程不是当前线程 }这个方法的核心逻辑如果队列为空 → 返回false没有等待者如果队列只有哨兵节点 → 返回false没有实际等待的线程如果队列中第一个等待线程是当前线程 → 返回false重入场景允许获取否则 → 返回true有其他线程在队列中等待当前线程不能插队4.4 公平锁加锁流程对比非公平锁: 线程到来 → 尝试CAS抢锁 → 失败 → tryAcquire再尝试 → 失败 → 入队等待 公平锁: 线程到来 → tryAcquire检查队列 → 有等待者则直接入队 → 入队等待 (没有lock开头的快速CAS抢锁)注意公平锁的lock()方法中没有开头的CAS抢锁// FairSync.lock() 直接调用 acquire没有快速尝试 final void lock() { acquire(1); }4.5 非公平的两次插队机会回顾非公平锁之所以非公平在于新来的线程有两次插队机会第一次插队lock()方法开头的compareAndSetState(0, 1)第二次插队tryAcquire()中的compareAndSetState(0, acquires)// NonfairSync.lock() final void lock() { if (compareAndSetState(0, 1)) // ← 插队机会1 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // nonfairTryAcquire() if (c 0) { if (compareAndSetState(0, acquires)) // ← 插队机会2 // ... }五、完整加锁流程对比总结5.1 非公平锁完整流程线程调用 lock() │ ▼ 【插队1】CAS(state0→1) 成功? ──是──▶ 设置owner → 返回 │ 否 ▼ tryAcquire() → nonfairTryAcquire() │ ├── state0? ──是──▶ 【插队2】CAS抢锁 │ │ │ ├── 成功 → 设置owner → 返回 │ └── 失败 → 继续 │ ├── owner当前线程? ──是──▶ state1 → 返回成功 │ └── 其他情况 → 返回失败 │ ▼ addWaiter() → 创建节点加入队列 │ ▼ acquireQueued() → 自旋等待 │ ├── 前驱是头节点且tryAcquire成功? ──是──▶ 设为新头节点 → 返回 │ └── 否则 → shouldPark... → parkAndCheck... → 阻塞5.2 公平锁完整流程线程调用 lock() → acquire(1) │ ▼ tryAcquire() (FairSync版本) │ ├── state0 且 队列中无等待者? ──是──▶ CAS抢锁成功 → 设置owner → 返回 │ ├── owner当前线程? ──是──▶ state1 → 返回成功 │ └── 其他情况 → 返回失败 │ ▼ addWaiter() → 创建节点加入队列 │ ▼ acquireQueued() → 自旋等待 (与非公平锁相同)5.3 关键区别总结表比较项非公平锁公平锁lock()开头快速CAS✅ 有❌ 无tryAcquire中state0时直接CAS抢锁不管队列检查队列无等待者才CAS插队次数2次0次吞吐量高低饥饿风险有无六、常见问题深度解答Q1为什么AQS队列的头节点是空节点哨兵答这是一个设计优化主要原因分离当前持有锁的线程和队列管理的职责释放锁时直接从头节点的后继节点开始唤醒逻辑统一避免空指针判断简化代码Q2为什么unparkSuccessor要从尾部向前遍历答因为并发入队时next指针可能不是最新的在enq()方法中先设置prev和CAStail最后才设置next如果刚设置完tail但还没设置next从头向后遍历会丢失新节点但prev指针一旦设置就不会改变所以从尾部向前遍历是可靠的Q3非公平锁为什么性能更好答因为减少了线程挂起/唤醒的次数公平锁线程释放锁后必须唤醒队列中的下一个线程非公平锁新来的线程可能直接抢到锁避免了唤醒开销在高并发场景下减少上下文切换能显著提升吞吐量Q4如何实现超时获取锁答AQS提供了tryAcquireNanos方法核心逻辑是javaprivate boolean doAcquireNanos(int arg, long nanosTimeout) { // ... for (;;) { // 尝试获取锁... nanosTimeout deadline - System.nanoTime(); if (nanosTimeout 0L) return false; // 超时返回 if (shouldParkAfterFailedAcquire(p, node) nanosTimeout spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); // 限时等待 // ... } }Q5AQS如何支持共享模式如Semaphore答共享模式与独占模式的区别独占模式state表示锁是否被占用0/1或重入次数共享模式state表示可用资源数量如Semaphore的许可数共享模式的节点释放后会传播唤醒后面的共享节点PROPAGATE状态七、总结7.1 核心知识点回顾组件作用state同步状态ReentrantLock中表示重入次数exclusiveOwnerThread当前持有锁的线程head/tail同步队列的头尾指针Node队列节点封装等待线程和前驱后继关系waitStatus节点状态SIGNAL表示需要唤醒后继LockSupport线程阻塞/唤醒的工具类7.2 关键设计思想CAS 自旋无锁化实现线程安全的入队操作模板方法模式AQS定义骨架子类实现钩子方法CLH队列变种双向链表便于取消和唤醒哨兵节点简化边界条件处理可重入设计state计数 owner线程判断7.3 一句话总结AQS通过一个volatile的state变量表示同步状态通过一个FIFO的双向CLH队列管理等待线程利用CAS自旋实现无锁入队通过LockSupport实现线程的阻塞与唤醒。非公平锁允许两次插队提升性能公平锁通过hasQueuedPredecessors检查保证先来后到。
Java并发编程:ReentrantLock与AQS原理剖析
发布时间:2026/5/23 23:34:05
前言在Java并发编程中ReentrantLock是一个非常重要的可重入互斥锁它比synchronized提供了更灵活的锁机制支持公平/非公平模式、可响应中断、超时等待、多条件变量等。但真正让ReentrantLock强大的是其底层依赖的AbstractQueuedSynchronizerAQS框架。AQS是JUCjava.util.concurrent包的基石CountDownLatch、Semaphore、ReentrantReadWriteLock等同步组件都基于它构建。本文将逐行解析源码彻底讲清楚AQS的核心数据结构state、CLH队列、Node非公平锁的完整加锁/解锁流程公平锁的实现原理及与非公平锁的区别同步队列的入队、阻塞、唤醒机制重入锁的实现原理一、AQS核心架构1.1 AQS是什么AbstractQueuedSynchronizer是一个抽象类它提供了一个FIFO队列来管理等待锁的线程并定义了一个int类型的state作为同步状态。子类只需要实现tryAcquire、tryRelease等钩子方法即可构造出自己的同步器。1.2 核心属性// AbstractQueuedSynchronizer 中的核心属性 // 同步状态volatile保证可见性 // 在ReentrantLock中state0表示锁空闲state0表示锁被持有值表示重入次数 private volatile int state; // 同步队列的头指针懒加载初始化时为空 private transient volatile Node head; // 同步队列的尾指针 private transient volatile Node tail;// AbstractOwnableSynchronizerAQS的父类中的属性 // 记录当前持有锁的线程独占模式下使用 private transient Thread exclusiveOwnerThread;1.3 Node节点结构同步队列中的每个节点都是一个Node对象static final class Node { // 节点等待模式 static final Node SHARED new Node(); // 共享模式 static final Node EXCLUSIVE null; // 独占模式 // 等待状态常量 static final int CANCELLED 1; // 线程已取消等待 static final int SIGNAL -1; // 当前节点释放锁后需要唤醒后继节点 static final int CONDITION -2; // 在条件队列中等待 static final int PROPAGATE -3; // 共享模式下需要传播唤醒 // 节点状态重要-1 SIGNAL1 CANCELLED0 初始状态 volatile int waitStatus; // 双向链表指针 volatile Node prev; // 前驱节点 volatile Node next; // 后继节点 // 该节点封装的线程 volatile Thread thread; // 指向条件队列的下一个节点Condition相关本文暂不详述 Node nextWaiter; }1.4 同步队列结构图重要特性队列是FIFO先进先出的头节点是一个哨兵节点一般不关联具体的线程或关联当前持有锁的线程每个节点封装一个等待的线程节点状态SIGNAL表示前驱节点释放锁后会唤醒我二、非公平锁完整源码解析2.1 加锁入口lock()// ReentrantLock 构造方法 public ReentrantLock(boolean fair) { sync fair ? new FairSync() : new NonfairSync(); } // 调用 lock() 时实际执行的是 sync.lock() public void lock() { sync.lock(); }NonfairSync.lock()实现final void lock() { // 【第一次插队机会】快速CAS尝试获取锁 // 如果锁空闲state0则直接抢锁成功 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 抢锁失败进入完整获取流程 }为什么叫非公平新来的线程可以不排队直接尝试抢锁。如果抢成功了队列中等待的线程只能继续等。2.2 acquireAQS模板方法public final void acquire(int arg) { // tryAcquire尝试获取锁包含重入逻辑 // addWaiter获取失败将当前线程封装成节点加入队列 // acquireQueued在队列中自旋等待 if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); // 如果等待过程中被中断过补上中断标记 }2.3 tryAcquire尝试获取锁AQS中的钩子方法protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }NonfairSync中的实现protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }核心逻辑 nonfairTryAcquirefinal boolean nonfairTryAcquire(int acquires) { final Thread current Thread.currentThread(); int c getState(); // 【情况1】锁空闲 if (c 0) { // 【第二次插队机会】CAS获取锁 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 【情况2】锁已被当前线程持有 → 重入 else if (current getExclusiveOwnerThread()) { int nextc c acquires; if (nextc 0) // 溢出保护 throw new Error(Maximum lock count exceeded); setState(nextc); // 不需要CAS因为只有当前线程才能执行到这里 return true; } // 【情况3】锁被其他线程持有 return false; }重入锁的关键同一个线程可以多次获取同一把锁每次重入state加1释放时需要释放相同次数。2.4 addWaiter入队操作private Node addWaiter(Node mode) { // 创建新节点封装当前线程modenull表示独占模式 Node node new Node(Thread.currentThread(), mode); // 【快速尝试】如果队列已存在尝试直接CAS追加到队尾 Node pred tail; if (pred ! null) { node.prev pred; if (compareAndSetTail(pred, node)) { pred.next node; return node; } } // 【兜底方案】队列不存在或CAS失败进入自旋入队 enq(node); return node; }2.5 enq自旋入队线程安全private Node enq(final Node node) { for (;;) { // 自旋直到入队成功 Node t tail; if (t null) { // 队列尚未初始化 // 创建哨兵节点作为头节点 if (compareAndSetHead(new Node())) tail head; // 头尾指向同一个哨兵节点 } else { // 标准入队操作 node.prev t; if (compareAndSetTail(t, node)) { t.next node; return t; // 返回前驱节点 } } } }为什么要创建哨兵节点有了哨兵节点队列永远不会为空简化了唤醒逻辑——每次从头节点的后继开始唤醒即可。2.6 acquireQueued排队等待这是最核心的等待逻辑final boolean acquireQueued(final Node node, int arg) { boolean failed true; try { boolean interrupted false; for (;;) { final Node p node.predecessor(); // 获取前驱节点 // 【关键判断1】前驱是头节点 → 说明当前节点是队列中等待最久的 if (p head tryAcquire(arg)) { // 获取锁成功将当前节点设置为新头节点 setHead(node); // 头节点会清空thread引用 p.next null; // help GC failed false; return interrupted; // 正常退出 } // 【关键判断2】获取锁失败判断是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt()) interrupted true; // 记录中断状态 } } finally { if (failed) cancelAcquire(node); // 异常情况取消获取 } }核心理解只有前驱是头节点的节点才有资格尝试获取锁保证FIFO获取失败时会阻塞自己等待前驱节点唤醒2.7 shouldParkAfterFailedAcquire决定是否阻塞private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws pred.waitStatus; // 【情况1】前驱状态已经是SIGNAL if (ws Node.SIGNAL) // 放心阻塞前驱释放锁时会唤醒我 return true; // 【情况2】前驱已取消等待 if (ws 0) { // 跳过所有取消的节点向前找到第一个有效节点 do { node.prev pred pred.prev; } while (pred.waitStatus 0); pred.next node; } // 【情况3】前驱状态为0或PROPAGATE else { // 将前驱状态设置为SIGNAL // 注意这里只是设置状态返回false外层会再尝试一次获取锁 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; // 当前不需要阻塞外层会再循环一次 }为什么设置SIGNAL后返回false因为设置完成后前驱节点可能刚好释放了锁所以应该再给当前节点一次获取锁的机会。2.8 parkAndCheckInterrupt阻塞线程private final boolean parkAndCheckInterrupt() { LockSupport.park(this); // 阻塞当前线程 return Thread.interrupted(); // 唤醒后返回中断状态并清除中断标记 }LockSupport.park()会让线程进入WAITING状态直到被unpark()唤醒。2.9 加锁完整流程图┌─────────────────────────────────────┐ │ lock() 调用 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ 快速CAS抢锁 (state0→1)? │ └─────────────────┬───────────────────┘ 成功 │ │ 失败 ▼ ▼ ┌─────────────────┐ ┌─────────────────────────┐ │ 获取锁成功 │ │ tryAcquire尝试获取锁 │ │ 设置owner线程 │ │ (含重入逻辑) │ └─────────────────┘ └───────────┬─────────────┘ ▼ 成功 │ │ 失败 ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ 获取锁成功 │ │ addWaiter │ └─────────────────┘ │ 入队 │ └────────┬────────┘ ▼ ┌─────────────────┐ │ acquireQueued │ │ 自旋等待 │ └────────┬────────┘ ▼ ┌─────────────────┐ │ 前驱是头节点? │ └────────┬────────┘ 是 │ 否 ▼ ┌─────────────────┐ │ tryAcquire成功? │ └────────┬────────┘ 是 │ 否 ▼ ┌─────────────────┐ │ 设置新头节点 │ │ 返回 │ └─────────────────┘ │ 否 ▼ ┌─────────────────┐ │ shouldPark... │ │ 确保前驱SIGNAL │ └────────┬────────┘ ▼ ┌─────────────────┐ │ parkAndCheck... │ │ 阻塞线程 │ └────────┬────────┘ │ (唤醒后重新自旋) ◄──────────────┘三、解锁流程完整解析3.1 unlock入口public void unlock() { sync.release(1); }3.2 releaseAQS释放模板public final boolean release(int arg) { if (tryRelease(arg)) { // 尝试释放锁 Node h head; // 头节点不为空且状态不为0说明有需要唤醒的后继节点 if (h ! null h.waitStatus ! 0) unparkSuccessor(h); // 唤醒后继节点 return true; } return false; }3.3 tryRelease释放锁重入减1protected 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); // 更新state不需要CAS因为只有当前线程能执行这里 return free; }关键点重入锁的释放是分层的。每次unlock()只减少一次计数只有计数归零时锁才真正释放。3.4 unparkSuccessor唤醒后继线程private void unparkSuccessor(Node node) { int ws node.waitStatus; if (ws 0) compareAndSetWaitStatus(node, ws, 0); // 清除SIGNAL状态 Node s node.next; // 如果后继节点为空或已取消从尾部向前找第一个有效节点 if (s null || s.waitStatus 0) { s null; // 为什么要从后往前因为并发环境下next指针可能不完整但prev是可靠的 for (Node t tail; t ! null t ! node; t t.prev) if (t.waitStatus 0) s t; } if (s ! null) LockSupport.unpark(s.thread); // 唤醒线程 }3.5 解锁流程图┌─────────────────────────────────────┐ │ unlock() 调用 │ └─────────────────┬───────────────────┘ ▼ ┌─────────────────────────────────────┐ │ tryRelease: state减1 │ │ 判断是否完全释放 (c 0)? │ └─────────────────┬───────────────────┘ 否 │ │ 是 ▼ ▼ ┌─────────────────┐ ┌─────────────────────────┐ │ 返回false │ │ 清空owner线程 │ │ 锁仍被持有 │ │ 返回true │ └─────────────────┘ └───────────┬─────────────┘ ▼ ┌─────────────────────────────┐ │ head ! null │ │ head.waitStatus ! 0? │ └───────────┬─────────────────┘ 是 │ 否 ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ unparkSuccessor │ │ 直接返回 │ │ 唤醒后继节点 │ └─────────────────┘ └─────────────────┘四、公平锁实现原理4.1 公平锁与非公平锁的核心区别公平锁与非公平锁的唯一区别在于尝试获取锁时是否检查队列中已有等待线程。特性非公平锁公平锁新线程能否插队能两次插队机会不能队列中等待线程的优先级无特殊保护等待最久的线程优先吞吐量高低可能产生饥饿是否默认选择是否4.2 公平锁的tryAcquire实现// FairSync 中的 tryAcquire 方法 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; }4.3 hasQueuedPredecessors检查队列中是否有等待线程public final boolean hasQueuedPredecessors() { Node t tail; Node h head; Node s; // 条件为真的情况队列不为空且头节点的后继节点不是当前线程 return h ! t // 队列中至少有2个节点头节点 至少一个等待节点 ((s h.next) null || // 头节点的后继为空极端情况 s.thread ! Thread.currentThread()); // 后继节点的线程不是当前线程 }这个方法的核心逻辑如果队列为空 → 返回false没有等待者如果队列只有哨兵节点 → 返回false没有实际等待的线程如果队列中第一个等待线程是当前线程 → 返回false重入场景允许获取否则 → 返回true有其他线程在队列中等待当前线程不能插队4.4 公平锁加锁流程对比非公平锁: 线程到来 → 尝试CAS抢锁 → 失败 → tryAcquire再尝试 → 失败 → 入队等待 公平锁: 线程到来 → tryAcquire检查队列 → 有等待者则直接入队 → 入队等待 (没有lock开头的快速CAS抢锁)注意公平锁的lock()方法中没有开头的CAS抢锁// FairSync.lock() 直接调用 acquire没有快速尝试 final void lock() { acquire(1); }4.5 非公平的两次插队机会回顾非公平锁之所以非公平在于新来的线程有两次插队机会第一次插队lock()方法开头的compareAndSetState(0, 1)第二次插队tryAcquire()中的compareAndSetState(0, acquires)// NonfairSync.lock() final void lock() { if (compareAndSetState(0, 1)) // ← 插队机会1 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // nonfairTryAcquire() if (c 0) { if (compareAndSetState(0, acquires)) // ← 插队机会2 // ... }五、完整加锁流程对比总结5.1 非公平锁完整流程线程调用 lock() │ ▼ 【插队1】CAS(state0→1) 成功? ──是──▶ 设置owner → 返回 │ 否 ▼ tryAcquire() → nonfairTryAcquire() │ ├── state0? ──是──▶ 【插队2】CAS抢锁 │ │ │ ├── 成功 → 设置owner → 返回 │ └── 失败 → 继续 │ ├── owner当前线程? ──是──▶ state1 → 返回成功 │ └── 其他情况 → 返回失败 │ ▼ addWaiter() → 创建节点加入队列 │ ▼ acquireQueued() → 自旋等待 │ ├── 前驱是头节点且tryAcquire成功? ──是──▶ 设为新头节点 → 返回 │ └── 否则 → shouldPark... → parkAndCheck... → 阻塞5.2 公平锁完整流程线程调用 lock() → acquire(1) │ ▼ tryAcquire() (FairSync版本) │ ├── state0 且 队列中无等待者? ──是──▶ CAS抢锁成功 → 设置owner → 返回 │ ├── owner当前线程? ──是──▶ state1 → 返回成功 │ └── 其他情况 → 返回失败 │ ▼ addWaiter() → 创建节点加入队列 │ ▼ acquireQueued() → 自旋等待 (与非公平锁相同)5.3 关键区别总结表比较项非公平锁公平锁lock()开头快速CAS✅ 有❌ 无tryAcquire中state0时直接CAS抢锁不管队列检查队列无等待者才CAS插队次数2次0次吞吐量高低饥饿风险有无六、常见问题深度解答Q1为什么AQS队列的头节点是空节点哨兵答这是一个设计优化主要原因分离当前持有锁的线程和队列管理的职责释放锁时直接从头节点的后继节点开始唤醒逻辑统一避免空指针判断简化代码Q2为什么unparkSuccessor要从尾部向前遍历答因为并发入队时next指针可能不是最新的在enq()方法中先设置prev和CAStail最后才设置next如果刚设置完tail但还没设置next从头向后遍历会丢失新节点但prev指针一旦设置就不会改变所以从尾部向前遍历是可靠的Q3非公平锁为什么性能更好答因为减少了线程挂起/唤醒的次数公平锁线程释放锁后必须唤醒队列中的下一个线程非公平锁新来的线程可能直接抢到锁避免了唤醒开销在高并发场景下减少上下文切换能显著提升吞吐量Q4如何实现超时获取锁答AQS提供了tryAcquireNanos方法核心逻辑是javaprivate boolean doAcquireNanos(int arg, long nanosTimeout) { // ... for (;;) { // 尝试获取锁... nanosTimeout deadline - System.nanoTime(); if (nanosTimeout 0L) return false; // 超时返回 if (shouldParkAfterFailedAcquire(p, node) nanosTimeout spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); // 限时等待 // ... } }Q5AQS如何支持共享模式如Semaphore答共享模式与独占模式的区别独占模式state表示锁是否被占用0/1或重入次数共享模式state表示可用资源数量如Semaphore的许可数共享模式的节点释放后会传播唤醒后面的共享节点PROPAGATE状态七、总结7.1 核心知识点回顾组件作用state同步状态ReentrantLock中表示重入次数exclusiveOwnerThread当前持有锁的线程head/tail同步队列的头尾指针Node队列节点封装等待线程和前驱后继关系waitStatus节点状态SIGNAL表示需要唤醒后继LockSupport线程阻塞/唤醒的工具类7.2 关键设计思想CAS 自旋无锁化实现线程安全的入队操作模板方法模式AQS定义骨架子类实现钩子方法CLH队列变种双向链表便于取消和唤醒哨兵节点简化边界条件处理可重入设计state计数 owner线程判断7.3 一句话总结AQS通过一个volatile的state变量表示同步状态通过一个FIFO的双向CLH队列管理等待线程利用CAS自旋实现无锁入队通过LockSupport实现线程的阻塞与唤醒。非公平锁允许两次插队提升性能公平锁通过hasQueuedPredecessors检查保证先来后到。