AQS(AbstractQueuedSynchronizer)是什么?请解释其在 Java 并发编程中的作用和实现原理。
参考回答
AbstractQueuedSynchronizer(简称 AQS)是 Java 并发包(java.util.concurrent)中的一个核心类,用于实现同步器的基础框架。AQS 提供了一种基于FIFO 等待队列的线程同步机制,支持独占锁和共享锁两种模式。
AQS 的作用
实现锁和同步器的基础:
AQS 是 ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier 等同步工具的核心实现基础。
管理线程的排队和唤醒:
AQS 使用一个CLH(CLH 队列)变种的 FIFO 队列,管理线程的排队和同步。
简化开发:
开发者通过继承 AQS 并实现其特定方法,可以轻松实现自定义同步器,而不需要关注底层细节。
AQS 的核心思想
状态管理:
AQS 通过一个 int state 字段表示同步状态,操作 state 的方法(getState()、setState()、compareAndSetState())是线程安全的。
FIFO 等待队列:
如果线程获取锁失败,会被封装为节点加入等待队列,按照 FIFO 排队。
模板方法模式:
子类通过重写 AQS 的模板方法(如 tryAcquire()、tryRelease())实现具体的同步逻辑。
详细讲解与拓展
1. AQS 的工作原理
AQS 主要包含以下关键组成部分:
1.1 同步状态(state)
state 是一个整数变量,用来表示同步器的状态。
例如,在独占锁中,state = 1 表示锁被占用,state = 0 表示锁空闲。
在共享锁中,state 可能表示当前持有锁的线程数。
常用方法:
getState():获取当前同步状态。
setState(int):设置同步状态。
compareAndSetState(int, int):原子更新状态,保证线程安全。
1.2 等待队列(CLH 队列)
AQS 使用一个变种的 CLH 队列(双向链表)来管理等待线程。
Node 节点:每个线程被封装为一个 Node 节点。
队列排队:线程尝试获取锁失败时会进入等待队列。
线程唤醒:当锁释放时,唤醒队列中的下一个线程。
示意图:
Head -> Node1 -> Node2 -> Node3 -> Tail
1.3 模板方法
AQS 提供了一系列可重写的模板方法,用于实现具体的同步逻辑。
方法名 描述
tryAcquire() 独占模式下尝试获取锁,子类实现具体逻辑。
tryRelease() 独占模式下尝试释放锁,子类实现具体逻辑。
tryAcquireShared() 共享模式下尝试获取锁,返回剩余许可数。
tryReleaseShared() 共享模式下尝试释放锁。
isHeldExclusively() 是否独占持有锁,常用于条件队列判断。
示例:实现独占锁的主要方法
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) { // CAS 操作
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
if (getState() 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0); // 释放锁
return true;
}
2. AQS 支持的模式
2.1 独占模式
一个线程独占锁,其他线程需要进入等待队列等待。
示例:ReentrantLock、CountDownLatch。
实现步骤:
尝试获取锁(tryAcquire())。
如果失败,将线程封装为节点加入等待队列。
当锁被释放时,唤醒队列中的下一个节点。
2.2 共享模式
多个线程可以同时获取锁。
示例:Semaphore、ReadWriteLock。
实现步骤:
尝试获取锁(tryAcquireShared())。
如果失败,将线程封装为节点加入等待队列。
当锁被释放时,唤醒多个节点。
- AQS 的具体实现:以 ReentrantLock 为例
ReentrantLock 是基于 AQS 实现的一个常用同步器。
核心逻辑:
独占模式下获取锁:
ReentrantLock 的 lock() 方法调用 AQS 的 acquire() 方法。
AQS 调用子类重写的 tryAcquire() 方法,判断是否能获取锁。
public void lock() {
sync.acquire(1);
}
protected final boolean tryAcquire(int arg) {
// 如果 state 为 0,尝试获取锁
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
释放锁
:
调用 release() 方法释放锁。
修改 state 为 0,并唤醒队列中的下一个节点。
- AQS 的优势
灵活性:
通过继承 AQS 并重写其方法,可以轻松实现自定义同步器。
高性能:
AQS 使用 CAS 操作保证线程安全,避免了繁琐的锁管理。
统一性:
AQS 提供了统一的同步机制,使得各种同步器(如锁、信号量)有一致的实现基础。
- 自定义同步器示例
以下示例实现一个简单的独占锁:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CustomLock {
private static class Sync extends AbstractQueuedSynchronizer {
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
if (getState() 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
protected boolean isHeldExclusively() {
return getState() 1;
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
使用示例:
public class TestCustomLock {
private static final CustomLock lock = new CustomLock();
public static void main(String[] args) {
Runnable task = () -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ” 获取锁”);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + ” 释放锁”);
}
};
Thread t1 = new Thread(task, “线程1”);
Thread t2 = new Thread(task, “线程2”);
t1.start();
t2.start();
}
}
总结
AQS 的核心作用:
提供统一的同步基础,支持独占模式和共享模式。
管理线程的排队和唤醒。
实现原理:
基于 state 表示同步状态。
使用 FIFO 队列管理线程的排队。
提供可扩展的模板方法。
实际应用:
AQS 是 ReentrantLock、Semaphore、CountDownLatch 等工具的实现基础。
通过继承 AQS,可以轻松实现自定义同步器。