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())。

如果失败,将线程封装为节点加入等待队列。

当锁被释放时,唤醒多个节点。

  1. 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,并唤醒队列中的下一个节点。

  1. AQS 的优势
    灵活性:
    通过继承 AQS 并重写其方法,可以轻松实现自定义同步器。

高性能:
AQS 使用 CAS 操作保证线程安全,避免了繁琐的锁管理。

统一性:
AQS 提供了统一的同步机制,使得各种同步器(如锁、信号量)有一致的实现基础。

  1. 自定义同步器示例
    以下示例实现一个简单的独占锁:

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,可以轻松实现自定义同步器。

发表评论

后才能评论