AQS学习
2026/8/21 14:37:32 网站建设 项目流程

1. AQS 简介

1.1 什么是 AQS

AQS(AbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent.locks)中的一个核心框架类,用于构建锁和其他同步器。

AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器。常见的同步器如ReentrantLockSemaphoreReentrantReadWriteLockSynchronousQueueFutureTask(JDK 1.7)等都是基于 AQS 实现的。开发者也可以利用 AQS 轻松构造符合自己需求的同步器。

2. AQS 核心原理

2.1 核心思想

AQS 的核心思想是:

  • 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态。
  • 如果被请求的共享资源被占用,则需要一套线程阻塞等待以及被唤醒时锁分配的机制。这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

2.2 CLH 队列详解

关于 AQS 类的 CLH 队列,源码注释中有详细说明:

Wait queue node class. The wait queue is a variant of a "CLH" (Craig, Landin, and Hagersten) lock queue. CLH locks are normally used for spinlocks. We instead use them for blocking synchronizers, but use the same basic tactic of holding some of the control information about a thread in the predecessor of its node. A "status" field in each node keeps track of whether a thread should block. A node is signalled when its predecessor releases. Each node of the queue otherwise serves as a specific-notification-style monitor holding a single waiting thread. The status field does NOT control whether threads are granted locks etc though. A thread may try to acquire if it is first in the queue. But being first does not guarantee success; it only gives the right to contend. So the currently released contender thread may need to rewait. To enqueue into a CLH lock, you atomically splice it in as new tail. To dequeue, you just set the head field.
2.2.1 主要含义

等待队列节点类。等待队列是"CLH"(Craig、Landin和Hagersten)锁定队列的变体。CLH锁通常用于自旋锁。AQS 使用它们来实现阻塞同步器,但采用相同的基本策略:在其前驱节点中保存有关线程的控制信息。

每个节点中的"状态"字段跟踪线程是否应该阻塞。节点在其前驱释放时收到信号。队列的每个节点充当一个特定通知样式的监视器,保存一个等待线程。状态字段不控制线程是否被授予锁等。如果线程是队列中的第一个,则可以尝试获取锁,但这不保证成功,只赋予竞争的权利。

入队到 CLH 锁只需要对"tail"执行一个原子操作,出队只需要更新"head"字段。

2.2.2 CLH 队列结构
+------+ prev +-----+ +-----+ head | | <---- | | <---- | | tail +------+ +-----+ +-----+
2.2.3 插入与删除机制
Insertion into a CLH queue requires only a single atomic operation on "tail", so there is a simple atomic point of demarcation from unqueued to queued. Similarly, dequeuing involves only updating the "head". However, it takes a bit more work for nodes to determine who their successors are, in part to deal with possible cancellation due to timeouts and interrupts. The "prev" links (not used in original CLH locks), are mainly needed to handle cancellation. If a node is cancelled, its successor is (normally) relinked to a non-cancelled predecessor. For explanation of similar mechanics in the case of spin locks, see the papers by Scott and Scherer at http://www.cs.rochester.edu/u/scott/synchronization/ We also use "next" links to implement blocking mechanics. The thread id for each node is kept in its own node, so a predecessor signals the next node to wake up by traversing next link to determine which thread it is. Determination of successor must avoid races with newly queued nodes to set the "next" fields of their predecessors. This is solved when necessary by checking backwards from the atomically updated "tail" when a node's successor appears to be null. (Or, said differently, the next-links are an optimization so that we don't usually need a backward scan.)

翻译:插入 CLH 队列只需要对"尾部"执行一个原子操作,所以从未排队到排队有一个简单的原子分界点。同样,退出队列只涉及更新"头部"。然而,节点需要更多的工作来确定它们的后继者是谁,部分原因是要处理由于超时和中断而可能导致的取消。

"prev"链接(未在原始 CLH 锁中使用)主要用于处理取消。如果某个节点被取消,则其后续节点(通常)将重新链接到未取消的前驱节点。

还使用"next"链接来实现阻塞机制。每个节点的线程 ID 都保存在自己的节点中,因此前驱节点通过遍历下一个链接来通知下一个节点唤醒,以确定它是哪个线程。确定后继节点必须避免与新排队的节点竞争,以设置前驱的"next"字段。

2.2.4 取消机制与条件队列
Cancellation introduces some conservatism to the basic algorithms. Since we must poll for cancellation of other nodes, we can miss noticing whether a cancelled node is ahead or behind us. This is dealt with by always unparking successors upon cancellation, allowing them to stabilize on a new predecessor, unless we can identify an uncancelled predecessor who will carry this responsibility. CLH queues need a dummy header node to get started. But we don't create them on construction, because it would be wasted effort if there is never contention. Instead, the node is constructed and head and tail pointers are set upon first contention. Threads waiting on Conditions use the same nodes, but use an additional link. Conditions only need to link nodes in simple (non-concurrent) linked queues because they are only accessed when exclusively held. Upon await, a node is inserted into a condition queue. Upon signal, the node is transferred to the main queue. A special value of status field is used to mark which queue a node is on. Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill Scherer and Michael Scott, along with members of JSR-166 expert group, for helpful ideas, discussions, and critiques on the design of this class.

翻译:取消给基本算法引入了一些保守性。由于必须轮询其他节点的取消,因此可能无法注意到被取消的节点是在我们之前还是之后。这是通过在取消时始终取消后续节点的标记来解决的,允许它们稳定在一个新的前驱节点上,除非能够确定一个未被取消的前驱节点将承担此责任。

CLH 队列需要一个虚拟头节点才能启动。但不会在构造时创建它们,因为如果不存在争用,这将浪费精力。相反,在第一次争用时构造节点并设置头指针和尾指针。

等待条件的线程使用相同的节点,但使用额外的链接。条件只需要链接简单(非并发)链接队列中的节点,因为它们只在独占时被访问。等待时,节点被插入到条件队列中。发出信号后,节点被转移到主队列。状态字段的特殊值用于标记节点所在的队列。

3. AQS 核心组件

3.1 同步状态(State)

AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。

3.2 状态操作方法

状态信息通过 protected 类型的getState()setState()compareAndSetState()进行操作:

  • getState():获取当前同步状态
  • setState(int newState):设置当前同步状态
  • compareAndSetState(int expect, int update):使用 CAS 设置当前状态,保证原子性

4. 总结

AQS 是 Java 并发编程的基石,通过 CLH 队列和状态管理机制,为各种同步器提供了统一的实现框架。理解 AQS 的工作原理对于深入掌握 Java 并发编程至关重要。

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

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

立即咨询