Java 并发锁体系全解:从 synchronized、Lock 到 AQS 底层原理与源码深度剖析

发布时间:2026/7/27 1:07:31
Java 并发锁体系全解:从 synchronized、Lock 到 AQS 底层原理与源码深度剖析 在 Java 并发编程体系中锁是解决多线程共享资源竞争、保证线程安全的核心机制。从 JVM 内置的synchronized隐式锁到 JUC 包提供的Lock显式锁再到支撑整个并发工具集的底层基石 AbstractQueuedSynchronizerAQS构成了一套层层递进、设计精妙的锁实现体系。本文将从上层 API 到底层源码完整拆解 Java 锁的核心原理覆盖独占模式与共享模式、公平锁与非公平锁、条件队列与双队列联动、读写锁实现等核心知识点结合 JDK 源码逐层剖析其设计思想与执行流程。一、Java 锁的两类核心实现synchronized 与 Lock1.1 synchronizedJVM 内置隐式锁synchronized是 JVM 层面实现的内置互斥锁核心特性是隐式获取与释放锁无需开发者手动干预进入同步代码块 / 同步方法时JVM 自动为当前线程加锁代码正常执行完成或抛出异常时自动释放锁底层依托对象头的 Mark Word 实现内置偏向锁、轻量级锁、重量级锁的自适应升级机制局限性突出不支持响应中断、不支持超时获取、仅能绑定一个等待条件无法适配复杂的并发协作场景1.2 LockAPI 层显式锁的灵活控制Lock是java.util.concurrent.locks包下的顶层接口属于 API 层面的显式锁需要手动调用方法完成锁的获取与释放相比synchronized具备更强的可操作性与场景适配能力支持可中断获取锁等待过程中可以响应线程中断终止等待支持超时获取锁在指定时长内尝试获取锁超时自动放弃避免永久阻塞支持绑定多个条件变量可实现更精细的等待 / 通知逻辑支持公平锁与非公平锁模式切换可根据业务场景选择调度策略Lock 接口的核心方法如下lock()阻塞式获取锁获取成功前持续等待不响应中断unlock()释放锁必须手动调用通常放在finally块中确保执行tryLock()非阻塞尝试获取锁立即返回布尔结果不阻塞线程lockInterruptibly()可中断式获取锁等待过程中响应中断并抛出异常newCondition()创建绑定当前锁的Condition条件变量一个锁可绑定多个独立条件JUC 中 Lock 的核心实现类ReentrantLock以及读写锁、信号量等工具其底层全部基于 AQS 框架实现。二、AQS并发同步器的核心基石AbstractQueuedSynchronizer简称 AQS是 Java 并发包中绝大多数同步工具ReentrantLock、Semaphore、CountDownLatch、ReentrantReadWriteLock 等的底层实现框架它定义了一套多线程访问共享资源的通用同步机制通过模板方法模式将通用逻辑封装子类仅需实现少量业务方法即可定制同步器。2.1 核心组件 1volatile 修饰的同步状态 stateAQS 通过一个被volatile修饰的整型变量state表示同步状态这是整个同步器的核心状态变量private volatile int state;独占模式下通常state0代表锁未被占用state1代表锁已被持有可重入场景下 state 随重入次数递增共享模式下state 代表可用的共享资源总数volatile保证了 state 在多线程间的可见性AQS 同时提供compareAndSetState(int expect, int update)方法基于 CAS 操作保证 state 修改的原子性避免多线程并发修改的线程安全问题。2.2 核心组件 2FIFO 双向同步队列当线程获取同步状态失败时AQS 会将线程封装为Node节点加入一个FIFO先进先出的双向链表同步队列通过队列实现线程的等待调度与唤醒管理。队列的节点为 AQS 的内部类Node核心字段如下static final class Node { // 节点等待状态 volatile int waitStatus; // 前驱节点指针 volatile Node prev; // 后继节点指针 volatile Node next; // 节点绑定的等待线程 volatile Thread thread; // 条件队列后继节点区分独占/共享模式 Node nextWaiter; }队列结构双向链表包含头节点head和尾节点tail头节点代表当前持有同步状态的节点后续节点按入队顺序等待节点模式分为独占模式Node.EXCLUSIVE和共享模式Node.SHARED通过nextWaiter字段区分三、AQS 独占式同步状态的获取与释放独占模式的核心特性是同一时间只能有一个线程持有同步状态对应 ReentrantLock 等互斥锁的实现。3.1 独占式获取acquire () 源码全解析acquire(int arg)是独占模式下获取同步状态的入口方法也是lock()方法的底层核心public final void acquire(int arg) { if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }执行流程分为四步尝试快速获取调用tryAcquire(arg)尝试直接获取同步状态成功则方法直接返回节点封装入队获取失败时调用addWaiter将当前线程封装为独占模式节点加入同步队列尾部队列自旋等待调用acquireQueued让节点在队列中自旋等待直到获取到同步状态中断标记补全若等待过程中线程被中断过最后调用selfInterrupt()补上中断标记分步核心源码解析tryAcquire子类实现的获取逻辑AQS 本身不实现具体的获取逻辑交由子类根据业务特性重写是模板方法模式的核心体现protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}例如 ReentrantLock 的公平锁与非公平锁就在该方法中实现了不同的抢占规则。addWaiter节点加入同步队列将当前线程封装为 Node 节点先通过 CAS 快速尝试加入队尾失败则进入自旋入队逻辑private Node addWaiter(Node mode) {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方法enq(node);return node;}3.enq自旋保证节点安全入队通过死循环 CAS保证高并发下节点一定能成功入队同时完成队列的懒初始化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;}}}}4.acquireQueued队列中自旋获取锁节点入队后进入自旋逻辑只有前驱节点是头节点时才尝试获取同步状态否则调整前驱状态后阻塞等待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; // 帮助GC回收旧头节点failed false;return interrupted;}// 获取失败判断是否需要阻塞当前线程if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt())interrupted true;}} finally {if (failed)cancelAcquire(node);}}3.2 waitStatus 节点等待状态详解Node 节点的waitStatus字段控制着线程的等待状态与唤醒逻辑JDK 1.8 中共有 5 种取值状态值常量名含义说明0-初始状态节点刚创建入队时的默认状态-1SIGNAL当前节点的后继节点处于阻塞状态当前节点释放同步状态或取消时必须唤醒后继节点1CANCELLED线程因超时、中断等原因取消等待节点作废不再参与同步竞争-2CONDITION节点位于 Condition 条件队列中等待条件满足此时不在同步队列内-3PROPAGATE共享模式下唤醒操作需要向后传播确保所有等待的共享节点都能被通知其中shouldParkAfterFailedAcquire方法是状态流转的核心负责调整前驱节点状态并判断当前线程是否可以安全阻塞private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws pred.waitStatus; // 前驱已设为SIGNAL当前线程可以安全阻塞 if (ws Node.SIGNAL) return true; // 前驱节点已取消向前跳过所有取消节点重新链接队列 if (ws 0) { do { node.prev pred pred.prev; } while (pred.waitStatus 0); pred.next node; } else { // 前驱为初始状态或PROPAGATECAS修改为SIGNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }3.3 正常流程与取消流程的状态流转正常流程下的状态流转在无取消、无异常的正常竞争场景下节点的状态流转如下节点入队新创建的节点 waitStatus 为初始值 0加入队列尾部状态修改节点执行shouldParkAfterFailedAcquire将前驱节点的 waitStatus 从 0 CAS 修改为 SIGNAL (-1)线程阻塞下一轮自旋再次尝试获取失败后确认前驱状态为 SIGNAL调用LockSupport.park()阻塞当前线程被唤醒头节点释放同步状态时唤醒后继节点当前线程从 park 中苏醒获取成功线程再次尝试获取同步状态成功后将自己设为新的头节点旧头节点出队完成一次状态流转取消流程下的状态流转当线程等待过程中发生异常、中断或超时会触发节点取消流程核心逻辑在cancelAcquire方法中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; // 情况1当前节点是尾节点更新队尾指针 if (node tail compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { int ws; // 情况2当前节点不是头节点的后继将前驱与后继节点链接 if (pred ! head ((ws pred.waitStatus) Node.SIGNAL || (ws 0 compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) pred.thread ! null) { Node next node.next; if (next ! null next.waitStatus 0) compareAndSetNext(pred, predNext, next); } else { // 情况3当前节点是头节点的后继直接唤醒后继节点 unparkSuccessor(node); } node.next node; // 帮助GC } }取消流程的核心是将节点标记为 CANCELLED调整队列双向指针把取消节点从同步队列中剔除保证队列的有效性。3.4 独占式同步状态释放release(int arg)是独占模式下释放同步状态的入口方法public final boolean release(int arg) { if (tryRelease(arg)) { Node h head; // 头节点不为空且状态非初始值需要唤醒后继 if (h ! null h.waitStatus ! 0) unparkSuccessor(h); return true; } return false; }执行流程调用tryRelease(arg)尝试释放同步状态由子类实现具体逻辑释放成功返回 true释放成功后检查头节点状态若头节点存在且 waitStatus 不为 0调用unparkSuccessor唤醒后继等待线程唤醒后继节点的unparkSuccessor方法private void unparkSuccessor(Node node) { int ws node.waitStatus; if (ws 0) compareAndSetWaitStatus(node, ws, 0); // 从后往前找第一个有效后继节点 Node s node.next; if (s null || s.waitStatus 0) { s null; for (Node t tail; t ! null t ! node; t t.prev) if (t.waitStatus 0) s t; } if (s ! null) LockSupport.unpark(s.thread); }为什么从队尾往前找后继节点因为节点入队时先设置 prev 指针、再 CAS 更新 tail、最后设置前驱的 next 指针从后往前遍历能保证不会漏掉刚入队的节点避免空指针问题。四、ReentrantLock公平锁与非公平锁源码对比ReentrantLock 是 AQS 独占模式最经典的实现也是可重入互斥锁的标准实现。它通过内部两个 AQS 子类实现了公平锁FairSync与非公平锁NonfairSync两种模式二者的核心差异完全体现在对tryAcquire方法的不同实现上。4.1 ReentrantLock 整体类结构ReentrantLock内部维护了一个继承自 AQS 的抽象内部类Sync作为公共逻辑基类再由FairSync和NonfairSync两个子类分别实现公平与非公平策略。public class ReentrantLock implements Lock, java.io.Serializable { // 同步器基类继承AQS abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); // 公共非公平获取逻辑供非公平锁直接调用 final boolean nonfairTryAcquire(int acquires) { ... } } // 非公平锁实现 static final class NonfairSync extends Sync { ... } // 公平锁实现 static final class FairSync extends Sync { ... } // 默认构造非公平锁 public ReentrantLock() { sync new NonfairSync(); } // 传入true指定公平锁 public ReentrantLock(boolean fair) { sync fair ? new FairSync() : new NonfairSync(); } }可重入特性两种锁都支持重入即同一个线程可以多次获取同一把锁state值随重入次数递增释放时对应递减直到 state 归 0 才算完全释放锁。重入逻辑在两种模式下完全一致。4.2 非公平锁 NonfairSync 源码解析非公平锁是ReentrantLock的默认实现核心特点是线程获取锁时不考虑队列中是否有等待线程直接尝试抢占抢占失败才进入队列排队。lock () 入口上来就插队非公平锁的lock()方法在进入 AQS 的acquire流程之前会先通过 CAS 直接尝试抢一次锁这是第一次插队机会。final void lock() { // 第一次插队直接CAS尝试把state从0改成1 if (compareAndSetState(0, 1)) // 抢锁成功设置当前线程为锁的持有者 setExclusiveOwnerThread(Thread.currentThread()); else // 抢占失败走AQS标准的acquire获取流程 acquire(1); }tryAcquire再次插队不判断队列非公平锁的tryAcquire直接调用基类的nonfairTryAcquire核心逻辑是只要锁空闲state0不管同步队列里有没有等待了很久的线程直接 CAS 抢锁这是第二次插队机会。protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // Sync基类中的公共非公平获取逻辑 final boolean nonfairTryAcquire(int acquires) { final Thread current Thread.currentThread(); int c getState(); // 锁处于空闲状态 if (c 0) { // 直接CAS抢锁不判断队列是否有等待线程 if (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 公平锁 FairSync 源码解析公平锁的核心原则是先来先服务FIFO线程获取锁时必须先检查同步队列中是否有其他线程在等待只有队列中没有更早的等待线程时才尝试获取锁否则直接进入队列排队。lock () 入口不插队直接走标准流程公平锁的lock()方法没有提前抢锁的操作直接调用 AQS 的acquire方法严格遵守排队规则。final void lock() { acquire(1); }tryAcquire公平性核心先判断队列公平锁的tryAcquire与非公平锁的唯一区别就是在锁空闲时会先调用hasQueuedPredecessors()判断队列中是否有前驱等待线程只有没有更早的等待者时才尝试获取锁。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; } } // 重入逻辑与非公平锁完全一致 else if (current getExclusiveOwnerThread()) { int nextc c acquires; if (nextc 0) throw new Error(Maximum lock count exceeded); setState(nextc); return true; } return false; }公平性核心hasQueuedPredecessors这是实现公平锁的关键方法用于判断同步队列中是否存在比当前线程等待更久的线程。public final boolean hasQueuedPredecessors() { Node t tail; Node h head; Node s; // 返回true队列中有更早的等待线程当前线程不能插队 return h ! t ((s h.next) null || s.thread ! Thread.currentThread()); }逻辑拆解h ! t头节点不等于尾节点说明队列不为空有线程在等待(s h.next) null头节点的后继节点为空说明有线程正在执行入队操作已经设置了 tail但还没设置 prev 的 next 指针此时认为队列中有等待者s.thread ! Thread.currentThread()后继节点存在但绑定的线程不是当前线程说明有其他线程比当前线程等待更久只要满足 “队列不为空且第一个等待线程不是当前线程”就返回 true当前线程必须排队从而保证公平性。4.4 公平锁 vs 非公平锁 核心对比对比维度非公平锁默认公平锁插队机会两次插队lock 时直接 CAS 抢锁tryAcquire 时再次抢锁无任何插队机会严格遵循 FIFO核心判断锁空闲就直接抢不关心等待队列锁空闲时必须先判断队列无更早等待者才抢吞吐量高线程挂起唤醒的上下文切换次数少低所有线程严格排队上下文切换频繁线程饥饿可能出现新来的线程一直插队队列中的线程长期获取不到锁不会出现所有线程按等待顺序依次获取适用场景绝大多数业务场景追求高吞吐量、高性能对执行顺序有严格要求需要避免饥饿的场景五、Condition 条件队列双队列联动与等待唤醒机制Condition是显式锁体系对 “等待 - 通知” 模式的进阶实现。它解决了内置锁Object.wait()/notify()只能绑定一个等待队列、无法实现精准唤醒的缺陷一个Lock可以绑定多个独立的Condition条件队列分别对应不同的等待条件典型应用如ArrayBlockingQueue的 “队列非空”“队列非满” 双条件设计。Condition本身是接口其核心实现是 AQS 的内部类ConditionObject它依托 AQS 的同步队列额外维护了一套独立的条件等待队列节点在 “条件队列” 与 “同步队列” 之间完成状态流转实现等待与唤醒的完整闭环。5.1 条件队列的结构设计核心字段与链表结构ConditionObject内部维护了一个单向链表结构的条件等待队列通过头尾两个指针定位队列节点复用 AQS 的Node内部类通过nextWaiter字段串联链表。public class ConditionObject implements Condition, java.io.Serializable { // 条件队列头节点第一个等待条件的节点 private transient Node firstWaiter; // 条件队列尾节点最后一个等待条件的节点 private transient Node lastWaiter; // 中断处理模式等待结束后抛出中断异常 private static final int THROW_IE -1; // 中断处理模式等待结束后重置中断标记 private static final int REINTERRUPT 1; }同步队列与条件队列的核心差异条件队列中的节点waitStatus固定为Node.CONDITION(-2)代表节点正在等待条件触发暂时脱离锁的竞争。维度同步队列AQS 内置条件队列Condition 维护链表结构双向链表prev/next 指针单向链表nextWaiter 指针等待目标等待获取锁同步状态等待某个业务条件满足节点状态0、SIGNAL(-1)、CANCELLED(1)、PROPAGATE(-3)CONDITION(-2)数量1 个 Lock 对应 1 个同步队列1 个 Lock 可对应 N 个条件队列节点归属未抢到锁的线程全部在此排队主动调用 await () 的线程在此等待一个节点同一时间只能存在于一个队列中要么在同步队列抢锁要么在条件队列等条件唤醒过程本质就是节点从条件队列迁移到同步队列的过程。5.2 await () 等待流程源码解析调用await()的前置条件当前线程必须已经持有对应 Lock 锁这和Object.wait()必须在同步块中调用是同一逻辑 —— 保证队列修改与锁释放的线程安全性。await()的完整执行逻辑可以概括为五步线程安全地创建节点加入条件队列尾部完全释放当前持有的锁处理可重入场景阻塞当前线程等待被唤醒或中断被唤醒后从条件队列迁移至同步队列在同步队列中重新竞争锁竞争成功后恢复执行await () 入口主流程public final void await() throws InterruptedException { // 1. 前置校验线程已中断则直接抛异常 if (Thread.interrupted()) throw new InterruptedException(); // 2. 创建CONDITION状态节点加入条件队列尾部 Node node addConditionWaiter(); // 3. 完全释放锁含重入次数返回释放前的state值 int savedState fullyRelease(node); int interruptMode 0; // 4. 自旋只要节点还没进入同步队列就持续阻塞 while (!isOnSyncQueue(node)) { // 阻塞当前线程 LockSupport.park(this); // 检查是否因中断被唤醒记录中断模式 if ((interruptMode checkInterruptWhileWaiting(node)) ! 0) break; } // 5. 节点已进入同步队列调用acquireQueued重新抢锁 if (acquireQueued(node, savedState) interruptMode ! THROW_IE) interruptMode REINTERRUPT; // 6. 清理条件队列中已取消的节点 if (node.nextWaiter ! null) unlinkCancelledWaiters(); // 7. 根据中断模式处理最终结果 if (interruptMode ! 0) reportInterruptAfterWait(interruptMode); }核心子方法解析addConditionWaiter加入条件队列创建状态为CONDITION的节点追加到条件队列尾部如果发现尾节点已取消先执行一次队列清理。private Node addConditionWaiter() {Node t lastWaiter;// 尾节点已取消先清理所有无效节点if (t ! null t.waitStatus ! Node.CONDITION) {unlinkCancelledWaiters();t lastWaiter;}// 新建节点状态为CONDITIONNode node new Node(Thread.currentThread(), Node.CONDITION);// 队列为空则设为头节点否则追加到尾部if (t null)firstWaiter node;elset.nextWaiter node;lastWaiter node;return node;}2.fullyRelease完全释放锁因为 ReentrantLock 是可重入锁线程可能多次获取锁state1调用 await 时必须一次性释放全部同步状态否则其他线程永远无法获取锁同时记录释放前的 state 值后续重新获锁时恢复重入次数。final int fullyRelease(Node node) { boolean failed true; try { int savedState getState(); // 一次性释放全部state if (release(savedState)) { failed false; return savedState; } else { // 释放失败说明当前线程未持有锁抛出非法监视器异常 throw new IllegalMonitorStateException(); } } finally { // 释放失败则标记节点为取消状态 if (failed) node.waitStatus Node.CANCELLED; } }5.3 signal () 唤醒流程源码解析signal()的核心作用并不是直接唤醒线程让它运行而是把满足条件的节点从条件队列迁移到同步队列尾部线程并不会立刻执行它需要进入同步队列后排队等待获取锁才能真正恢复运行。signal () 主流程public final void signal() { // 前置校验当前线程必须持有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first firstWaiter; if (first ! null) // 唤醒条件队列中第一个有效节点 doSignal(first); }doSignal遍历唤醒首个有效节点从条件队列头开始遍历找到第一个未取消的节点执行转移操作如果节点已取消则继续往后找。private void doSignal(Node first) { do { // 头节点后移若后续无节点则尾指针置空 if ( (firstWaiter first.nextWaiter) null) lastWaiter null; first.nextWaiter null; // 转移节点到同步队列失败则继续处理下一个 } while (!transferForSignal(first) (first firstWaiter) ! null); }transferForSignal节点转移核心这是双队列联动的核心方法完成节点从条件队列到同步队列的迁移与状态转换final boolean transferForSignal(Node node) { // 1. CAS修改状态从CONDITION改为初始0 // 修改失败说明节点已被取消直接返回false if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; // 2. 调用enq方法将节点加入同步队列尾部返回前驱节点 Node p enq(node); int ws p.waitStatus; // 3. 检查前驱节点状态 // - 前驱已取消或无法设置为SIGNAL则直接唤醒当前线程 // - 让线程自己去同步队列中竞争并清理无效节点 if (ws 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }这里有一个关键设计signal () 不一定会立刻 unpark 线程。如果前驱节点状态正常且成功设为 SIGNAL就不唤醒线程等前驱节点释放锁时再统一唤醒减少不必要的上下文切换。5.4 双队列完整状态流转结合 AQS 同步队列节点的全生命周期流转对应线程的等待与唤醒全过程持有锁阶段线程 A 成功获取锁成为同步队列头节点对应的执行线程state0。进入条件队列线程 A 调用await()创建 waitStatusCONDITION 的节点追加到条件队列尾部同时调用fullyRelease完全释放锁state 归零。阻塞等待线程 A 执行LockSupport.park()进入阻塞状态此时节点仅存在于条件队列脱离同步队列。触发唤醒线程 B 获取锁后调用signal()取出条件队列头节点CAS 将 waitStatus 从 CONDITION 改为 0调用enq将节点加入同步队列尾部。同步队列排队节点进入同步队列后遵循独占锁的排队规则前驱节点设为 SIGNAL线程继续阻塞等待。重新获锁前驱节点释放锁时唤醒该节点线程竞争锁成功重新设置 state 为之前的重入次数从 await 方法返回继续执行业务代码