Java中的AQS(AbstractQueuedSynchronizer)是构建锁和同步器的核心框架,位于java.util.concurrent.locks
包下。它通过一个int类型的状态变量和一个FIFO队列,提供了一种高效且可扩展的同步机制。下面从概念、原理和使用三个方面详细解析:
一、AQS的核心概念
1. 同步状态(State)
- AQS使用一个
int
类型的state
变量来表示同步状态(如锁的持有状态) - 通过
getState()
、setState()
、compareAndSetState()
等方法进行操作 - 例如,ReentrantLock用state表示锁的重入次数
2. 队列(CLH队列)
- 当线程获取锁失败时,会被封装成Node节点加入到队列中
- 队列是一个双向链表,遵循FIFO原则,保证公平性
- 队列的头节点是当前持有锁的节点
3. 模板方法模式
- AQS定义了获取锁和释放锁的框架,但将具体实现留给子类
- 子类需要实现
tryAcquire()
、tryRelease()
等方法来定义锁的语义
二、AQS的工作原理
1. 核心数据结构
java
// AQS的简化结构
public abstract class AbstractQueuedSynchronizer {
// 同步状态
private volatile int state;
// 队列头节点
private transient volatile Node head;
// 队列尾节点
private transient volatile Node tail;
// 节点类(简化)
static final class Node {
// 节点状态:CANCELLED、SIGNAL、CONDITION等
volatile int waitStatus;
// 前驱和后继节点
volatile Node prev;
volatile Node next;
// 关联的线程
volatile Thread thread;
}
}
2. 获取锁流程
java
// acquire方法简化流程
public final void acquire(int arg) {
if (!tryAcquire(arg)) { // 尝试获取锁(子类实现)
// 获取失败,加入队列
Node node = addWaiter(Node.EXCLUSIVE);
// 进入队列后自旋或阻塞
acquireQueued(node, arg);
}
}
3. 释放锁流程
java
// release方法简化流程
public final boolean release(int arg) {
if (tryRelease(arg)) { // 尝试释放锁(子类实现)
Node h = head;
if (h != null) {
// 唤醒队列中的下一个节点
unparkSuccessor(h);
}
return true;
}
return false;
}
4. 关键机制
- CAS操作 :通过
compareAndSetState()
实现无锁的原子操作 - LockSupport :通过
park()
和unpark()
实现线程的阻塞和唤醒 - 状态设计:Node节点的waitStatus控制线程的状态转换
三、AQS的使用方式
1. 自定义独占锁示例
java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class SimpleLock {
private static class Sync extends AbstractQueuedSynchronizer {
// 尝试获取锁
@Override
protected boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁
@Override
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 判断是否持有锁
@Override
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 boolean isLocked() {
return sync.isHeldExclusively();
}
}
2. 使用示例
java
public class LockExample {
private final SimpleLock lock = new SimpleLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
四、AQS在JUC中的应用
1. ReentrantLock
- 使用AQS实现可重入锁
- 公平锁和非公平锁的区别在于
tryAcquire()
方法的实现
2. Semaphore
- 使用AQS的共享模式实现信号量
- state表示可用的许可数量
3. CountDownLatch
- 使用AQS的共享模式实现倒计时门闩
- state表示计数器的初始值
4. ReentrantReadWriteLock
- 使用AQS实现读写锁分离
- state的高16位表示读锁计数,低16位表示写锁计数
五、AQS的优缺点
优点
- 统一的框架:提供了一套统一的同步机制,简化了锁和同步器的实现
- 高性能:基于CAS操作,避免了重量级锁的性能开销
- 可扩展性:可以通过继承AQS来自定义同步器
缺点
- 实现复杂:需要深入理解AQS的工作原理才能正确实现
- 调试困难:队列操作和状态转换较为复杂,调试难度较大
六、总结
AQS是Java并发包的核心组件,它通过一个int类型的状态变量和一个FIFO队列,提供了一种高效且可扩展的同步机制。AQS的设计思想是模板方法模式,它定义了获取锁和释放锁的框架,将具体实现留给子类。通过AQS,我们可以很方便地实现各种锁和同步器,如ReentrantLock、Semaphore、CountDownLatch等。理解AQS的工作原理,对于深入理解Java并发编程具有重要意义。