在Java中,AQS(AbstractQueuedSynchronizer)是一个提供了一个用于实现依赖于先进先出等待队列的阻塞锁和相关同步器(如信号量、事件等)的框架。它是Java并发工具包中许多同步器的基础,例如ReentrantLock、CountDownLatch、Semaphore、ReentrantReadWriteLock等。
AQS使用一个整数表示同步状态,并通过一个内部的FIFO队列来管理那些获取同步状态失败的线程。这个框架允许使用者通过实现方法来管理其同步状态,这些方法包括:
tryAcquire
:尝试获取资源,成功则返回true,失败则返回false。tryRelease
:尝试释放资源,成功则返回true,失败则返回false。isHeldExclusively
:查询当前线程是否独占地持有资源。
AQS的设计允许多个线程对资源的共享访问(共享模式)或独占访问(独占模式)。例如,ReentrantReadWriteLock可以允许多个读线程同时访问资源(共享模式),但写线程则需要独占访问。
AQS还通过一个内部队列来管理那些等待获取资源的线程,当资源被释放时,头部的线程(或者一部分线程,在共享模式下)会被唤醒,尝试重新获取资源。
AbstractQueuedSynchronizer的实现细节和它如何精确地工作涉及到复杂的并发编程技巧,包括使用volatile变量和CAS操作(比较并交换),这些都是确保线程安全和高性能的关键。
原理讲解
从Java源码的角度详细了解AbstractQueuedSynchronizer(AQS)的工作原理,我们需要关注几个关键的组件和概念:
1. 同步状态(Sync State)
AQS使用一个名为state
的单一整数来跟踪同步器的状态。这个状态的具体含义取决于AQS的子类如何使用它。例如,在ReentrantLock
中,它表示持锁的计数;在Semaphore
中,它表示剩余的许可数。
java
private volatile int state;
2. 节点和CLH队列
AQS内部使用一个叫做CLH(Craig, Landin, and Hagersten)锁队列的变种,它是一个虚拟的双向队列(双向链表)。每个参与同步状态竞争的线程都被封装成一个节点(Node类)并加入此队列。
java
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
}
3. 等待队列的管理
节点在队列中的管理涉及到将线程以FIFO的方式加入队列尾部,以及在条件满足时,将其从队列中移除或唤醒。
- 入队操作:如果获取同步状态失败,线程被包装成节点并加入队列尾部。
- 出队操作:当线程释放同步状态时,它会唤醒队列中的下一个节点(如果有的话)。
java
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
4. 获取和释放同步状态
AQS定义了模板方法acquire
和release
,这些方法分别用于获取和释放同步状态。具体行为则通过覆盖tryAcquire
和tryRelease
等方法实现。
- 获取状态 :
acquire
方法调用tryAcquire
尝试直接获取资源,失败则将线程加入等待队列。 - 释放状态 :
release
方法调用tryRelease
来改变同步状态,并尝试唤醒队列中的后续节点。
java
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
5. 等待/通知机制
AQS还提供条件变量的支持,允许线程在某个条件成立之前等待,当条件可能成立时接收通知。
这个机制是通过Condition
接口的实现,比如ConditionObject
,它使用了一个与主同步队列分离的条件队列来管理那些等待特定条件的线程。
java
public class ConditionObject implements Condition, java.io.Serializable {
private transient Node firstWaiter;
private transient Node lastWaiter;
public void await() throws InterruptedException {
...
}
public void signal() {
...
}
}
总结来说,AQS是通过精细的多线程管理和内部状态控制,为多种同步结构提供了强大而灵活的底层支持。
简单案例
一个常见的使用AbstractQueuedSynchronizer(AQS)的场景是创建一个自定义的同步器。例如,我们可以实现一个简单的二元闭锁(Binary Latch),它允许一个或多个线程等待,直到某个操作完成。
在这个示例中,闭锁的状态将是0(关闭)或1(打开)。一旦闭锁被打开,所有的等待线程都可以通过。闭锁一旦打开就不能再关闭,这类似于CountDownLatch
的一个非重用版本。
示例代码
这里是一个使用AQS实现的二元闭锁的简单例子:
java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class BinaryLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
// 返回是否处于锁定状态
protected int tryAcquireShared(int acquires) {
// 如果闭锁是开的(state == 1),则这个操作成功,否则失败
return (getState() == 1) ? 1 : -1;
}
// 尝试释放共享锁,这将打开闭锁
protected boolean tryReleaseShared(int releases) {
// 闭锁打开,state设置为1
setState(1);
return true;
}
}
private final Sync sync = new Sync();
public void release() {
sync.releaseShared(1);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean isReleased() {
return sync.getState() == 1;
}
public static void main(String[] args) throws InterruptedException {
BinaryLatch latch = new BinaryLatch();
Thread worker = new Thread(() -> {
System.out.println("Worker is processing...");
try {
Thread.sleep(2000); // 模拟工作耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
latch.release();
System.out.println("Worker has finished processing.");
});
Thread waiter = new Thread(() -> {
System.out.println("Waiter is waiting for the worker to finish...");
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Waiter has been notified that worker has finished.");
});
worker.start();
waiter.start();
worker.join();
waiter.join();
}
}
代码详解
在这个代码示例中:
BinaryLatch
类使用内部类Sync
,后者扩展了AbstractQueuedSynchronizer
。tryAcquireShared
方法决定线程是否能够获取共享锁。这里,它检查闭锁的状态,如果状态为1(开启状态),则返回1允许所有等待的线程继续执行。tryReleaseShared
方法用于将状态设置为1(打开闭锁),这样所有等待的线程都可以继续执行。- 在主方法中,一个工作线程模拟了处理过程,完成后释放闭锁。另一个线程等待闭锁被释放。