一、AQS是什么?为什么它这么重要?
想象一下你去银行办理业务,所有人都在无序地挤向柜台,那场面肯定乱成一锅粥。而在Java世界里,AQS(AbstractQueuedSynchronizer)就像银行的那个智能排队系统,它默默地维持着线程访问共享资源的秩序。
AQS是Java并发包里的一个基础框架,像ReentrantLock、CountDownLatch这些我们常用的工具,内部都是靠它实现的。它的核心思想很简单:当资源被占用时,其他线程会进入等待队列;当资源释放时,再按顺序唤醒等待的线程。
举个生活中的例子:AQS就像医院挂号系统。当所有医生都在忙时,新来的病人会取号排队(进入等待队列);当有医生空闲时,系统会按顺序叫下一个号(唤醒等待线程)。
二、AQS的核心实现原理
AQS内部维护了一个双向链表结构的等待队列,以及一个表示资源状态的volatile变量state。我们来看个最简单的实现示例:
// 技术栈:Java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
// 自定义一个最简单的互斥锁
class SimpleLock extends AbstractQueuedSynchronizer {
// 尝试获取锁
protected boolean tryAcquire(int arg) {
// CAS操作:如果state是0(未锁定),就设置为1(锁定)
return compareAndSetState(0, 1);
}
// 尝试释放锁
protected boolean tryRelease(int arg) {
// 把state设置为0(未锁定)
setState(0);
return true;
}
// 判断是否持有锁
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 加锁方法
public void lock() {
acquire(1);
}
// 解锁方法
public void unlock() {
release(1);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
SimpleLock lock = new SimpleLock();
new Thread(() -> {
lock.lock();
try {
System.out.println("线程1获取锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("线程1释放锁");
}
}).start();
new Thread(() -> {
lock.lock();
try {
System.out.println("线程2获取锁");
} finally {
lock.unlock();
System.out.println("线程2释放锁");
}
}).start();
}
}
这个示例展示了AQS最基本的用法。当线程1获取锁后,线程2会进入等待队列,直到线程1释放锁后,线程2才会被唤醒。
三、AQS的两种模式
AQS支持两种资源共享模式:
- 独占模式(Exclusive):同一时刻只有一个线程能获取资源,比如ReentrantLock
- 共享模式(Shared):多个线程可以同时获取资源,比如Semaphore
我们来看个共享模式的例子:
// 技术栈:Java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
// 自定义一个简单的信号量
class SimpleSemaphore extends AbstractQueuedSynchronizer {
private final int maxPermits;
public SimpleSemaphore(int maxPermits) {
this.maxPermits = maxPermits;
setState(maxPermits); // 初始化可用许可数
}
// 尝试获取许可(共享模式)
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining)) {
return remaining;
}
}
}
// 尝试释放许可
protected boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next > maxPermits) {
throw new Error("超过最大许可数");
}
if (compareAndSetState(current, next)) {
return true;
}
}
}
// 获取许可
public void acquire() {
acquireShared(1);
}
// 释放许可
public void release() {
releaseShared(1);
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
SimpleSemaphore semaphore = new SimpleSemaphore(3); // 允许3个线程同时访问
for (int i = 0; i < 5; i++) {
new Thread(() -> {
semaphore.acquire();
try {
System.out.println(Thread.currentThread().getName() + "获取许可");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + "释放许可");
}
}).start();
}
}
}
在这个例子中,我们创建了一个最多允许3个线程同时访问的信号量。当第4个线程尝试获取许可时,它会进入等待状态,直到有线程释放许可。
四、AQS的高级特性
AQS还提供了一些高级功能,比如条件变量(Condition)。这就像是在银行排队时,专门为办理特定业务的人开设的专属窗口。我们来看个例子:
// 技术栈:Java
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
class AdvancedLock extends AbstractQueuedSynchronizer {
// 省略tryAcquire等基础方法...
// 创建一个条件变量
public Condition newCondition() {
return new ConditionObject();
}
}
public class ConditionExample {
private static AdvancedLock lock = new AdvancedLock();
private static Condition condition = lock.newCondition();
private static boolean ready = false;
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println("等待线程:等待条件满足");
while (!ready) {
condition.await(); // 等待条件
}
System.out.println("等待线程:条件已满足,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
lock.lock();
try {
Thread.sleep(2000); // 模拟工作
ready = true;
condition.signal(); // 通知等待线程
System.out.println("通知线程:条件已满足");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
}
}
这个例子展示了如何使用AQS的条件变量功能。一个线程可以等待某个条件成立,另一个线程在条件成立时通知等待的线程。
五、AQS的应用场景
AQS在Java并发编程中无处不在,下面是一些典型应用:
- ReentrantLock:可重入锁,比synchronized更灵活
- Semaphore:控制同时访问资源的线程数量
- CountDownLatch:等待多个线程完成
- CyclicBarrier:让一组线程互相等待
- FutureTask:异步任务的基础实现
我们来看个CountDownLatch的例子:
// 技术栈:Java
import java.util.concurrent.CountDownLatch;
public class Race {
public static void main(String[] args) throws InterruptedException {
int runnerCount = 5;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(runnerCount);
for (int i = 0; i < runnerCount; i++) {
new Thread(() -> {
try {
System.out.println("选手" + Thread.currentThread().getId() + "准备就绪");
startSignal.await(); // 等待发令枪
System.out.println("选手" + Thread.currentThread().getId() + "开始跑步");
Thread.sleep((long)(Math.random() * 3000)); // 模拟跑步时间
System.out.println("选手" + Thread.currentThread().getId() + "到达终点");
doneSignal.countDown(); // 完成计数
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(2000); // 裁判准备
System.out.println("发令枪响!");
startSignal.countDown(); // 开始比赛
doneSignal.await(); // 等待所有选手完成
System.out.println("比赛结束!");
}
}
这个例子模拟了跑步比赛,所有选手等待发令枪响,裁判等待所有选手完成比赛。
六、AQS的优缺点
优点:
- 高性能:相比传统的synchronized,AQS提供了更细粒度的控制
- 灵活性:可以基于AQS实现各种同步工具
- 公平性:支持公平和非公平两种模式
缺点:
- 复杂性:直接使用AQS需要理解其内部机制
- 容易出错:如果实现不当,可能导致死锁或资源泄漏
注意事项:
- 在tryAcquire/tryRelease等方法中不要执行耗时操作
- 确保锁一定会被释放,通常在finally块中释放
- 注意避免死锁,特别是可重入锁的多次获取
七、总结
AQS是Java并发包的基石,它通过一个精巧的等待队列机制,实现了各种同步工具。理解AQS的工作原理,能帮助我们更好地使用Java提供的并发工具,也能在需要时自定义适合特定场景的同步器。
虽然AQS的源码看起来有些复杂,但它的核心思想其实很简单:管理一个等待队列,通过CAS操作维护状态。在实际开发中,我们通常不需要直接继承AQS,而是使用基于它实现的工具类,如ReentrantLock、Semaphore等。
记住,并发编程的核心是管理对共享资源的访问,而AQS为我们提供了一套高效、灵活的解决方案。掌握了AQS,你就掌握了Java并发编程的关键。
评论