☰
AQS 被整个重写过:`waitStatus` 只存在于 JDK 13 及以前
2026/10/14 1:22:11 网站建设 项目流程

本文首发于 CSDN,同步发布于其他技术社区。文中每个结论都给了可自行验证的出处。

AQS 的原理八股文里,几乎每个人都背过Node的四个等待状态:CANCELLED = 1、SIGNAL = -1、CONDITION = -2、PROPAGATE = -3。如果你在 JDK 17 或 21 的源码里搜这四个常量,会发现一个都搜不到。这不是你看错了——AQS 在 JDK 14 被整块重写过。同一类问题还发生在ReentrantReadWriteLock上,它的容量上限在 JDK 25 也变了。

AQS 的核心是什么?官方 Javadoc 的原话

AbstractQueuedSynchronizer类 Javadoc 的第一段是:

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues.This class is designed to be a useful basisfor most kinds of synchronizers that rely on a single atomicintvalue to represent state. Subclasses must define the protected methods that change this state, and which define what that state means in terms of this object being acquired or released.

关键在最后半句:state 的含义由子类定义。AQS 自己不知道这个int是重入次数还是信号量许可数。

所以「AQS 的 state 就是重入次数」这个说法不准确。对ReentrantLock来说确实如此(源码注释:Uses AQS state to represent the number of holds on the lock.),对Semaphore来说它就是剩余许可数,对CountDownLatch来说又是计数值。

state字段本身是private volatile int state;,三个访问器getState、setState、compareAndSetState都是protected final。Javadoc 给getState的说明是This operation has memory semantics of a volatile read——它是个 volatile 读写语义的封装。

「CLH 队列」这个说法在哪儿?Javadoc 里其实没有

「AQS 的等待队列是 CLH 队列的变体」这句话是对的,但它不在公开 Javadoc 里。原文在源码/* Overview. */注释块中:

The wait queue is avariant of a “CLH” (Craig, Landin, and Hagersten) lock queue. CLH locks are normally used for spinlocks. We instead use them for blocking synchronizers by including explicit (“prev” and “next”) links plus a “status” field…

所以严格说:它是 OpenJDK 官方注释,属于一手来源,但标注成「Javadoc 原文」就不严谨了。同一块注释里还给了队列结构图,明确prev/next是双向链接。

顺带一个常被误传的点:Javadoc 里没有出现过Template Method这个词。我在整个AbstractQueuedSynchronizer.java里检索过template,零命中。官方表达的是这个思想——the other methods in this class carry out all queuing and blocking mechanics——但没用这个术语。

五个 protected 方法默认会怎样?

AQS 有五个留给子类覆写的方法,它们的默认实现全是抛异常:

protectedbooleantryAcquire(intarg){thrownewUnsupportedOperationException();}protectedbooleantryRelease(intarg){thrownewUnsupportedOperationException();}protectedinttryAcquireShared(intarg){thrownewUnsupportedOperationException();}protectedbooleantryReleaseShared(intarg){thrownewUnsupportedOperationException();}protectedbooleanisHeldExclusively(){thrownewUnsupportedOperationException();}

所以「不实现tryAcquire会编译错误」是错的——编译能过,运行时报UnsupportedOperationException。Javadoc 里还有一句很关键的限定:

Defining these methods is theonlysupported means of using this class.All other methods are declaredfinalbecause they cannot be independently varied.

阻塞用的底层机制是LockSupport。许可语义的官方原文在LockSupport自己的 Javadoc 里:Unlike with Semaphores though, permits do not accumulate. There is at most one.——每个线程最多攒一个许可,unpark两次不会让后面的park连续通过两次。

waitStatus的四个取值,为什么现在查不到了?

这是本篇最值得记住的一条。AQS 的Node内部类在JDK 14 被整块重写,官方依据是 OpenJDK 的 JBS 提交JDK-8229442 “AQS and lock classes refresh”(2019-09-14)。

对比一下前后两套结构:

JDK 13 及以前JDK 14 及以后
状态字段volatile int waitStatus;volatile int status;
取消CANCELLED = 1CANCELLED = 0x80000000
后继需唤醒SIGNAL = -1已移除,改用WAITING = 1
条件等待CONDITION = -2COND = 2
共享传播PROPAGATE = -3已移除
共享/独占标记Node.SHARED/Node.EXCLUSIVE标记位SharedNode/ExclusiveNode具体子类
入队方法addWaiter/shouldParkAfterFailedAcquireacquire/cleanQueue

也就是说,新版里SIGNAL、PROPAGATE、CONDITION、waitStatus这四个名字全部不存在。一个不看版本、直接背 JDK 8 源码的八股答案,在 JDK 14 之后就已经失准了。

同一轮重写还改掉了ReentrantLock里的方法名:nonfairTryAcquire只在 JDK 13 及以前存在,JDK 14 起拆成了initialTryLock()+tryAcquire()两段。公平锁的判断逻辑没变(FairSync依然检查队列里有没有前驱),只是承载它的方法换了名字。

补充一句:这篇里的 JBS 编号、源码结构和 Javadoc 原文我都逐条比对过。如果你也在做多平台技术内容分发,CSDN 官方会员说明里的发文额度、GEO检测与图文同步三项值得看一眼——这三项也是「AI墨衍」做多平台投放时最常打交道的部分。

公平锁的原话是「更慢」,不是「性能差不多」

ReentrantLock的 Javadoc 在公平性那一节的原文是:

The constructor for this class accepts an optionalfairnessparameter. When settrue, under contention, locks favor granting access to thelongest-waiting thread. Otherwise this lock does not guarantee any particular access order.Programs using fair locks accessed by many threads may display lower overall throughput (i.e., are slower; often much slower) than those using the default setting, but have smaller variances in times to obtain locks and guarantee lack of starvation.

官方措辞是often much slower,不是「二者差不多」。同一段还补了两句限定:

  • fairness of locks does not guarantee fairness of thread scheduling——锁公平不等于线程调度公平;
  • the untimed tryLock() method does not honor the fairness setting——不带超时的tryLock()不看公平设置,这一条很少被提到。

默认构造器是非公平的,源码就一行:public ReentrantLock() { sync = new NonfairSync(); }。Javadoc 也明说This is equivalent to using ReentrantLock(false).

另外,「ReentrantLock 性能比 synchronized 好」这个说法在官方文档里找不到依据。官方承诺的是功能更多——可中断、可超时、可试获取、多个Condition,这些都写在Lock接口的 Javadoc 里。功能多和性能好是两件事。

ReentrantReadWriteLock的 65535 上限也变了

「读写锁最多支持 65535 个读锁」这条结论在 JDK 24 及以前是对的,因为旧版把 state 的高 16 位给读锁、低 16 位给写锁:

staticfinalintSHARED_SHIFT=16;staticfinalintMAX_COUNT=(1<<SHARED_SHIFT)-1;// 65535

JDK 25 起这套位切分改了,官方依据是 JDK-8352971:

JDK 24 及以前JDK 25 及以后
父类AbstractQueuedSynchronizerAbstractQueuedLongSynchronizer
SHARED_SHIFT1632
MAX_COUNT65535Integer.MAX_VALUE
Javadoc 容量描述a maximum of 65535 recursive write locks and 65535 read locks同步改为Integer.MAX_VALUE

有一条官方规则倒是从没变过,而且经常被讲反:锁降级允许,锁升级不允许。Javadoc 原文是downgrading from the write lock to a read lock... However, upgrading from a read lock to the write lock is *not* possible.

Condition那一侧也有一句容易被忽略的官方说明。newCondition()的 Javadoc 里写着Waiting threads are signalled in FIFO order.——条件队列的唤醒是 FIFO 的,但重新获取锁的顺序不保证,官方说它和线程最初获取锁的规则一致。

JDK 24 之后synchronized不再 pin 住虚拟线程

JEP 491(Release 24)解决的是这个长期痛点,Summary 原文是:

Improve the scalability of Java code that usessynchronizedmethods and statements by arranging for virtual threads that block in such constructs to release their underlying platform threads for use by other virtual threads.

对写代码的直接影响,官方在 Description 里说得很清楚:Once the synchronized keyword no longer pins virtual threads, you can choose between synchronized and the APIs in the java.util.concurrent.locks package based solely upon which best solves the problem at hand.,并且明确You need not revert code that has been migrated to use ReentrantLock back to using synchronized.

顺带一提,JEP 491 同时移除了jdk.tracePinnedThreads这个诊断参数,理由是它会导致输出混乱,改成保留 JFR 事件jdk.VirtualThreadPinned给其他 pinning 场景用。

常见问题

Q:AQS 用 CAS 改 state,会不会有 ABA 问题?

AQS 源码里没有任何关于 ABA 的说明,检索ABA零命中。compareAndSetState底层是U.compareAndSetInt,确实不带版本号。实际的判断依据是:state 只被同一个持有者递增递减,语义上不构成 ABA 场景。但这是推理,不是官方文档结论,写文章时区分一下。

Q:lockInterruptibly会立即响应中断吗?

Lock接口的 Javadoc 描述是等待期间可以被中断并抛InterruptedException,同时清除中断状态。但官方也留了口子:an implementation can favor responding to an interrupt over normal method return。「立即」不是官方措辞。

Q:AQS 的队列是 FIFO 吗?

队列本身是 FIFO 的,但 AQS 的 Javadoc 明确写了它不自动强制 FIFO 获取策略。是否按 FIFO 授予锁,取决于子类的tryAcquire怎么写。


本文引用的 JBS 编号、源码结构与 Javadoc 原文,都可以在 OpenJDK 提交记录和 Oracle 的 Javadoc 页面里逐条核对,建议按自己项目实际使用的 JDK 版本复核一遍。资料整理自 OpenJDK 源码、Oracle 官方 Javadoc 与 JEP 文档。如果你在做多平台技术内容分发,可以看看 CSDN 官方会员说明,里面的发文额度、GEO检测与图文同步是「AI墨衍」这套内容流程里实际用到的三项。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询