AbstractQueuedSynchronizer (AQS) 是 Java 并发包(java.util.concurrent)中的一个基础框架,用于构建线程同步工具如锁、信号量、屏障等。AQS 提供了一种基于 FIFO 队列的等待机制,可以处理线程的阻塞和唤醒,帮助开发者实现各种复杂的同步器。了解 AQS 的设计和工作原理有助于我们深入理解 Java 并发编程。
主要特性和概念
1. 独占模式(Exclusive Mode)
在独占模式下,资源只能被一个线程占有。例如,ReentrantLock 就是基于独占模式实现的。使用独占模式的主要方法有:
acquire(int arg):独占模式的获取方法,如果不能立即获取,则进入等待队列。release(int arg):独占模式的释放方法,释放资源并唤醒等待队列中的一个线程。
2. 共享模式(Shared Mode)
在共享模式下,资源可以被多个线程同时占有。例如,Semaphore 和 CountDownLatch 就是基于共享模式实现的。使用共享模式的主要方法有:
acquireShared(int arg):共享模式的获取方法,如果不能立即获取,则进入等待队列。releaseShared(int arg):共享模式的释放方法,释放资源并可能唤醒等待队列中的一个或多个线程。
3. FIFO 等待队列
AQS 通过一个 FIFO 队列管理等待线程,当某个线程无法获取资源时,它会进入这个队列等待,并在资源变得可用时被唤醒。每个节点(Node)代表一个等待的线程。
4. 状态字段
AQS 使用一个整数(state)字段来表示同步状态。子类通过重写 tryAcquire(int arg)、tryRelease(int arg)、tryAcquireShared(int arg) 和 tryReleaseShared(int arg) 等方法来定义具体的同步语义。
关键方法
以下是 AQS 中几个核心方法及其作用:
acquire(int arg):尝试以独占模式获取资源。如果获取失败则进入等待队列,直到资源可用再次尝试获取。release(int arg):以独占模式释放资源,释放成功后将唤醒等待队列中的一个线程。acquireShared(int arg):尝试以共享模式获取资源。如果获取失败则进入等待队列,直到资源可用再次尝试获取。releaseShared(int arg):以共享模式释放资源,释放后可能唤醒等待队列中的多个线程。tryAcquire(int arg):自定义独占模式的资源获取逻辑。tryRelease(int arg):自定义独占模式的资源释放逻辑。tryAcquireShared(int arg):自定义共享模式的资源获取逻辑。tryReleaseShared(int arg):自定义共享模式的资源释放逻辑。isHeldExclusively():查询当前资源是否由当前线程独占。
工作原理
AQS 的工作流程可以大致分为以下几个步骤:
-
获取资源:调用
acquire或acquireShared尝试获取资源。- 如果资源可用,则更新状态字段
state并退出。 - 如果资源不可用,将当前线程包装成 Node 节点并加入等待队列。
- 如果资源可用,则更新状态字段
-
进入队列:线程被插入到 FIFO 等待队列中等待唤醒。
- 使用
ReentrantLock之类的机制来保证队列的线程安全性,防止多个线程同时操作队列。
- 使用
-
释放资源:调用
release或releaseShared释放资源。- 更新状态字段
state。 - 唤醒等待队列中的后继线程,使其尝试再次获取资源。
- 更新状态字段
-
唤醒等待线程:当有线程释放资源时,等待队列中的线程依次被唤醒,并尝试重新获取资源。
代码示例
以下是一个基于 AQS 实现的简单独占锁:
java复制代码
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class SimpleExclusiveLock {
private final Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
}
小结
Java AQS 是一个强大的框架,通过提供可扩展的同步机制,使开发者能够高效地实现各种同步工具。理解 AQS 的核心概念和工作原理,包括独占模式和共享模式如何通过 FIFO 等待队列管理,状态字段如何影响同步状态,是理解和实现高效并发编程的重要基础。
具体实现工具
Java 并发包(java.util.concurrent)中许多重要的线程同步工具都是基于 AbstractQueuedSynchronizer (AQS) 实现的。以下是一些常见的基于 AQS 实现的线程同步工具及其具体逻辑:
1. ReentrantLock
ReentrantLock 是一种可重入的互斥锁,它有公平锁和非公平锁两种模式。
具体逻辑:
- 独占模式:使用独占模式(Exclusive Mode)获取锁。
- 重入:支持可重入,即同一线程可以多次获得同一把锁,每次加锁计数加一,解锁时计数减一,直到计数为零时释放锁。
- 公平锁:如果设置为公平锁,则等待时间最长的线程优先获取锁。
- 非公平锁:如果设置为非公平锁,则当前线程会尝试立即获取锁,如果失败则进入等待队列。
核心代码示例:
java复制代码
public class ReentrantLock {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
@Override
protected boolean isHeldExclusively() {
return getState() != 0 && getExclusiveOwnerThread() == Thread.currentThread();
}
@Override
protected boolean tryRelease(int releases) {
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
int c = getState() - releases;
if (c == 0) {
setExclusiveOwnerThread(null);
setState(c);
return true;
}
setState(c);
return false;
}
}
static final class NonfairSync extends Sync {
@Override
void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
@Override
protected boolean tryAcquire(int acquires) {
return // 非公平锁的获取逻辑
}
}
static final class FairSync extends Sync {
@Override
void lock() {
acquire(1);
}
@Override
protected boolean tryAcquire(int acquires) {
return // 公平锁的获取逻辑
}
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
2. ReentrantReadWriteLock
ReentrantReadWriteLock 是一种读写锁,它允许多个读操作并发进行,但写操作是互斥的,并且读写操作之间也是互斥的。
具体逻辑:
- 共享模式:用来实现读锁,多个读线程可以同时获取读锁。
- 独占模式:用来实现写锁,写锁是互斥的,只有一个线程可以获取写锁。
- 读写互斥:当一个线程持有写锁时,其他线程不能获取读锁或写锁。
核心代码示例:
java复制代码
public class ReentrantReadWriteLock {
private final ReadLock readerLock;
private final WriteLock writerLock;
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// Read lock and write lock logic
// 共享模式获取
protected int tryAcquireShared(int acquires) {
return // 读锁获取逻辑
}
// 共享模式释放
protected boolean tryReleaseShared(int releases) {
return // 读锁释放逻辑
}
// 独占模式获取
protected boolean tryAcquire(int acquires) {
return // 写锁获取逻辑
}
// 独占模式释放
protected boolean tryRelease(int releases) {
return // 写锁释放逻辑
}
}
static final class FairSync extends Sync {
// 公平策略的同步器实现
}
static final class NonfairSync extends Sync {
// 非公平策略的同步器实现
}
public static class ReadLock {
public void lock() {
sync.acquireShared(1);
}
public void unlock() {
sync.releaseShared(1);
}
}
public static class WriteLock {
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock();
writerLock = new WriteLock();
}
public ReadLock readLock() { return readerLock; }
public WriteLock writeLock() { return writerLock; }
}
3. CountDownLatch
CountDownLatch 是一个同步工具类,用于多个线程等待某些事件的完成。
具体逻辑:
- 共享模式:用于实现等待器,调用
await的线程将等待计数器变为零。 - 计数器:初始计数值由构造函数设置,调用
countDown会减少计数器,计数器到零时,所有等待的线程会被唤醒。
核心代码示例:
public class CountDownLatch {
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
@Override
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
public CountDownLatch(int count) {
sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
}
4. Semaphore
Semaphore 是一种信号量,用于限制某个资源的并发访问数。
具体逻辑:
- 共享模式:用于实现信号量,调用
acquire获取一个许可,调用release释放一个许可。 - 计数器:表示当前可用的许可数量。
核心代码示例:
java复制代码
public class Semaphore {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits);
}
@Override
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
@Override
protected boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (compareAndSetState(current, next))
return true;
}
}
}
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
}
5. CyclicBarrier
CyclicBarrier 是一种同步工具,用于使一组线程相互等待,直到所有线程都到达某个屏障点。
具体逻辑:
- 独占模式:用于实现屏障,在所有线程到达屏障点之前,调用
await的线程将被阻塞。 - 计数器:计数器到零时,所有等待的线程会被唤醒。
核心代码示例:
java复制代码
public class CyclicBarrier {
private final Sync sync;
private static class Generation {
boolean broken = false;
}
private final class Sync extends AbstractQueuedSynchronizer {
private Generation generation = new Generation();
Sync(int parties) {
setState(parties);
}
int parties() {
return getState();
}
int arriveAndAwaitAdvance() {
for (;;) {
int c = getState();
if (c == 0)
return 0;
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
if (nextc == 0) {
nextGeneration();
return 0;
} else {
for (;;) {
try {
wait();
break;
} catch (InterruptedException e) {
notifyAll();
}
}
return nextc;
}
}
}
}
private void nextGeneration() {
notifyAll();
generation = new Generation();
}
}
public CyclicBarrier(int parties) {
if (parties <= 0) throw new IllegalArgumentException();
sync = new Sync(parties);
}
public int await() throws InterruptedException, BrokenBarrierException {
return sync.arriveAndAwaitAdvance();
}
}
总结
基于 AbstractQueuedSynchronizer (AQS) 的这些同步工具通过灵活使用独占(Exclusive)和共享(Shared)两种模式,以及 FIFO 等待队列机制,实现了高效的线程同步。通过理解这些同步工具的具体逻辑,可以更好地设计和优化基于并发的应用程序。