在 Java 并发编程中,设计出一种高效、通用且可扩展的同步机制一直是一个重要的课题。AbstractQueuedSynchronizer
(AQS)的出现,正是为了满足这一需求。本文将深入探讨 AQS 设计的动机及其意义,并通过源码解析详细解释其实现过程,最后结合实际应用场景展示 AQS 的强大功能。
高效的同步机制
在多线程环境下,线程需要协同工作,避免资源竞争和死锁问题。然而,线程的管理和调度是一个复杂且开销较大的任务。高效的同步机制可以极大地减少线程切换和上下文切换的开销,从而提升系统的性能。AQS 通过引入 CLH 队列(Craig, Landin, and Hagersten 队列),有效地解决了线程的排队和调度问题。
java
// CLH 队列的基本结构
static final class Node {
static final Node SIGNAL = new Node(); // 用于标记节点需要唤醒
static final Node CANCELLED = new Node(); // 用于标记节点已取消
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 节点对应的线程
volatile int waitStatus; // 节点等待状态
// 默认构造函数,用于初始化head或SHARED标记
Node() {
}
// 用于addWaiter方法
Node(Thread thread, Node mode) {
this.next = null;
this.thread = thread;
this.waitStatus = 0;
}
}
通用的同步机制
在 Java 并发包中,有多种不同类型的锁和同步器,如 ReentrantLock
、Semaphore
、CountDownLatch
等。虽然它们的功能和使用场景各不相同,但其底层实现逻辑具有相似性。AQS 提供了一套通用的模板方法,使得这些锁和同步器可以基于同一套机制进行实现,从而减少了代码冗余和维护成本。
java
// 以ReentrantLock为例,它通过扩展AQS来实现锁的功能
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
// 内部抽象类,扩展AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 尝试非公平地获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
// 非公平锁实现
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
public ReentrantLock() {
sync = new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
可扩展性
AQS 提供了一套可扩展的基础设施,允许开发者根据具体需求自定义同步器。通过继承 AQS 并重写其模板方法,开发者可以轻松实现自己的同步器。AQS 的设计使得这些自定义同步器能够在高并发环境下保持高效和稳定。
java
// 自定义同步器的示例
class CustomSync 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 class CustomLock {
private final CustomSync sync = new CustomSync();
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
提高代码可读性和可维护性
通过 AQS 提供的通用模板方法,锁和同步器的实现逻辑变得更加清晰和可维护。开发者只需关注具体同步器的核心逻辑,而不必重复实现线程管理和调度的细节。这大大提高了代码的可读性和可维护性。
保证线程安全
AQS 使用 volatile
关键字和 CAS(Compare-And-Swap)操作来保证同步状态的线程安全。这些机制确保了在高并发环境下,线程对共享资源的操作是安全的。
java
// 共享变量,使用volatile修饰保证线程可见性
private volatile int state;
// 原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
应用场景
我们已经了解了 AQS 的设计动机和基本结构,接下来我们将探讨它在实际应用中的一些常见场景。
1. 实现互斥锁 (ReentrantLock)
互斥锁是最常见的锁类型,用于确保在同一时刻只有一个线程可以访问共享资源。ReentrantLock
是 Java 提供的一个可重入锁,它基于 AQS 实现。
java
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 尝试非公平地获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
public ReentrantLock() {
sync = new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
在 ReentrantLock 中,通过扩展 AQS 并重写其模板方法 tryAcquire
和 tryRelease
,实现了互斥锁的功能。ReentrantLock 提供了可重入性,即同一线程可以多次获取锁,并在释放时相应地减少持有次数。
2. 实现读写锁 (ReentrantReadWriteLock)
读写锁允许多个线程同时读取共享资源,但在写操作时需要独占锁。ReentrantReadWriteLock
提供了一对读锁和写锁,它们基于 AQS 实现。
java
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
private final ReadLock readerLock;
private final WriteLock writerLock;
final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 获取读锁
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 获取写锁
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
abstract boolean readerShouldBlock();
abstract boolean writerShouldBlock();
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
if (w == 0 ||
current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
}
public ReentrantReadWriteLock() {
sync = new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
}
在 ReentrantReadWriteLock 中,通过扩展 AQS 并重写其模板方法 tryAcquire
和 tryRelease
,实现了读写锁的功能。读锁和写锁分别用于控制读操作和写操作的并发访问。
3. 实现计数信号量 (Semaphore)
计数信号量用于控制对有限资源的访问。Semaphore
可以限制同时访问资源的最大线程数,它基于 AQS 实现。
java
public class Semaphore implements java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // 溢出
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void release() {
sync.releaseShared(1);
}
}
在 Semaphore 中,通过扩展 AQS 并重写其模板方法 tryAcquireShared
和 tryReleaseShared
,实现了信号量的功能。计数信号量用于限制同时访问资源的线程数。
4. 实现倒计时门闩 (CountDownLatch)
倒计时门闩用于等待一组线程完成各自的任务。CountDownLatch
基于 AQS 实现,通过一个计数器来控制线程的等待和释放。
java
public class CountDownLatch {
private final Sync sync;
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
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) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getCount();
}
}
在 CountDownLatch 中,通过扩展 AQS 并重写其模板方法 tryAcquireShared
和 tryReleaseShared
,实现了倒计时门闩的功能。倒计时门闩用于等待一组线程完成各自的任务。
结论
AQS 为 Java 并发包中的各种锁和同步器提供了一个通用的基础设施。通过扩展 AQS 并重写其模板方法,开发者可以轻松实现互斥锁、读写锁、信号量、倒计时门闩等不同类型的同步器,以满足不同的并发需求。AQS 的设计不仅提高了代码的可读性和可维护性,还保证了高并发环境下的线程安全。