Java并发面试题 - 说说AQS 吧?
什么是AQS?
AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发包中的一个核心框架,它为构建锁和同步器提供了基础实现。许多常见的并发工具如ReentrantLock、Semaphore、CountDownLatch等都是基于AQS构建的。
AQS的核心思想是:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态;如果共享资源被占用,则需要一套线程阻塞等待以及被唤醒时锁分配的机制。
AQS的核心结构
AQS内部维护了一个FIFO的双向队列(CLH队列的变种),用来存储阻塞的线程。同时使用一个volatile的int类型state变量来表示同步状态。
AQS的工作原理
1. 获取资源(acquire)
2. 释放资源(release)
AQS的关键方法
AQS提供了以下关键方法供子类实现:
tryAcquire(int arg)
- 尝试以独占方式获取资源tryRelease(int arg)
- 尝试以独占方式释放资源tryAcquireShared(int arg)
- 尝试以共享方式获取资源tryReleaseShared(int arg)
- 尝试以共享方式释放资源isHeldExclusively()
- 判断当前线程是否独占资源
AQS的应用示例
1. 实现一个简单的互斥锁
class Mutex extends AbstractQueuedSynchronizer {
// 尝试获取锁
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 锁是否被占用
public boolean isLocked() {
return getState() != 0;
}
}
2. AQS在ReentrantLock中的应用
ReentrantLock中的公平锁和非公平锁都是基于AQS实现的:
AQS的两种模式
- 独占模式:同一时刻只有一个线程能获取资源,如ReentrantLock
- 共享模式:多个线程可以同时获取资源,如Semaphore、CountDownLatch
AQS的状态管理
AQS使用一个volatile的int变量state
来表示同步状态,通过CAS操作来保证原子性:
- 在ReentrantLock中,state表示锁的持有计数
- 在Semaphore中,state表示可用许可的数量
- 在CountDownLatch中,state表示计数器的值
AQS的等待队列
AQS的等待队列是一个CLH锁队列的变种,具有以下特点:
- 双向链表结构
- 每个节点代表一个等待线程
- 节点状态包括:
- CANCELLED(1):线程已取消
- SIGNAL(-1):后继节点需要被唤醒
- CONDITION(-2):节点在条件队列中
- PROPAGATE(-3):共享模式下传播
AQS的公平性与非公平性
基于AQS实现的同步器可以支持公平和非公平两种模式:
- 公平模式:严格按照FIFO顺序获取锁
- 非公平模式:允许插队,可能提高吞吐量但可能导致饥饿
AQS的优缺点
优点:
- 提供了锁和同步器的框架,减少了实现复杂度
- 高性能的同步机制
- 支持中断和超时
- 支持公平和非公平两种模式
缺点:
- 理解和使用较为复杂
- 需要正确实现模板方法
- 不适合所有同步场景
总结
AQS是Java并发包的核心基础,它通过一个FIFO队列管理等待线程,使用state变量表示同步状态,并提供了可重写的模板方法供子类实现特定同步语义。理解AQS对于深入掌握Java并发编程至关重要,它不仅是许多并发工具的基础,也体现了优秀的并发设计思想。
通过合理使用AQS,我们可以构建各种高性能的同步器,满足不同的并发控制需求。