简介: 本文深入解析Java并发核心组件AQS(AbstractQueuedSynchronizer),从其设计动机、核心思想到源码实现,系统阐述了AQS如何通过state状态、CLH队列和模板方法模式构建通用同步框架,并结合独占与共享模式分析典型应用,最后通过自定义锁的实战案例,帮助读者掌握其原理与最佳实践。
商业创意
在Java并发编程中,ReentrantLock、Semaphore、CountDownLatch等强大的同步工具类为我们解决了各种各样的线程协作难题。你是否曾好奇,这些功能各异的工具类,其底层是如何实现的?
答案就藏在 java.util.concurrent.locks.AbstractQueuedSynchronizer(AQS)这个看似晦涩的抽象类中。AQS是整个JUC包同步器的基石和灵魂,它用一个优雅的框架,封装了构建锁和同步器的核心细节。理解AQS,就如同获得了打开Java并发世界宝库的钥匙,让你能从“使用者”进阶为“理解者”和“创造者”。
在AQS出现之前,如果要实现一个自定义的锁或同步器,开发者需要直面复杂的线程排队、阻塞、唤醒等底层操作。这些操作不仅容易出错,而且难以优化。
AQS的设计目标:提供一个通用的、模板化的框架,让开发者可以专注于实现同步状态的获取与释放逻辑(即“什么是同步条件”),而将复杂的线程排队、等待、唤醒等机制(即“如何管理排队线程”)交给AQS底层统一实现。
简单来说:AQS负责管理“排队”,你(同步器的实现者)只负责定义“什么时候放人进去”。
AQS的核心思想可以用一个经典的银行办事大厅的比喻来理解:
这是一个volatile int类型的变量,是所有操作的核心。AQS提供了三种原子方法来操作它:
无法立即获取锁的线程会被包装成一个Node节点。每个Node包含了:
这些Node节点共同组成了一个双向的FIFO队列,即同步队列。AQS通过这个队列来管理所有等待线程的排队和唤醒。
AQS支持两种工作模式,这决定了资源是如何被线程获取的。
一次只有一个线程能成功获取资源,如ReentrantLock。
多个线程可以同时成功获取资源,如Semaphore、CountDownLatch。
以独占模式的acquire方法为例,看看AQS是如何工作的:
// AQS中的模板方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 步骤1:子类尝试获取一次资源
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 步骤2&3:获取失败,则加入队列并等待
selfInterrupt(); // 如果是中断模式,则补上中断标记
}
release流程相对简单:
理解了原理,让我们动手实现一个最简单的(非重入)互斥锁Mutex。
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
/**
* 一个简单的不可重入互斥锁
*/
public class Mutex implements Lock {
// 静态内部类,继承AQS,实现具体的同步逻辑
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于占用状态(state为1表示占用,0表示空闲)
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
// 尝试获取锁(AQS模板方法acquire会调用此方法)
@Override
protected boolean tryAcquire(int acquires) {
// 断言: acquires 必须为 1
assert acquires == 1;
// 使用CAS操作,尝试将state从0改为1
if (compareAndSetState(0, 1)) {
// 成功!设置当前线程为独占所有者
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
// CAS失败,获取锁失败
return false;
}
// 尝试释放锁(AQS模板方法release会调用此方法)
@Override
protected boolean tryRelease(int releases) {
// 断言: releases 必须为 1
assert releases == 1;
// 如果状态已经是0,说明锁已经是空闲的,释放操作异常
if (getState() == 0) {
throw new IllegalMonitorStateException();
}
// 释放锁,将独占所有者清空
setExclusiveOwnerThread(null);
// 注意:state的volatile写放在最后,保证之前的修改对获取锁的线程可见
setState(0);
return true;
}
}
// 将具体工作代理给Sync对象
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1); // 调用AQS的模板方法,AQS会去调用我们重写的tryAcquire
}
@Override
public void unlock() {
sync.release(1); // 调用AQS的模板方法,AQS会去调用我们重写的tryRelease
}
// 其他Lock接口方法(略实现)
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, java.util.concurrent.TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public java.util.concurrent.Condition newCondition() {
// 简单实现,不支持Condition
throw new UnsupportedOperationException();
}
}
使用这个自定义的Mutex锁:
public class MutexExample {
private static final Mutex lock = new Mutex();
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> {
lock.lock(); // 获取我们自定义的锁
try {
for (int i = 0; i < 10000; i++) {
count++;
}
} finally {
lock.unlock(); // 释放锁
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + count); // 正确输出 20000
}
}
通过这个简单的例子,你可以清晰地看到AQS是如何工作的:我们只定义了“获取锁的条件是CAS(0,1)成功”,而线程排队、阻塞、唤醒等复杂机制全部由AQS的acquire和release模板方法帮我们完成了。
AQS是Java并发大师Doug Lea的作品,其设计之精妙,堪称艺术品。理解它,不仅是为了应对面试,更是为了提升我们对并发编程本质的认识,培养设计复杂系统的架构能力。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。