Java高级面试必问:AQS 到底是什么?

此篇主要针对大家感兴趣的AQS详细说明,欢迎评论区指正!

一、AQS究竟是什么?

AQS(AbstractQueuedSynchronizer) 不是一个具体的锁,而是一个构建锁和同步器的框架。你可以把它想象成汽车工厂的"底盘平台":同样的底盘可以造出轿车、SUV、卡车等各种车型。

二、为什么需要AQS?解决了什么问题?

1. 问题背景

在AQS出现之前,Java只有synchronized关键字实现同步,但它存在局限:

  • 无法实现公平锁
  • 没有超时等待机制
  • 没有条件变量的精细控制
  • 无法中断一个正在等待的线程

2. AQS的解决方案

AQS提供了一个通用的并发控制框架,让开发者可以:

  • 自定义各种锁策略
  • 实现复杂的同步需求
  • 获得更好的性能

三、AQS的架构和工作原理

核心结构:CLH队列 + 状态变量

关键代码:AQS核心方法

java 复制代码
public abstract class AbstractQueuedSynchronizer {    private volatile int state;    private transient volatile Node head;    private transient volatile Node tail;    static final class Node {        volatile Node prev;        volatile Node next;        volatile Thread thread;        volatile int waitStatus;

AQS 的两种同步模式

独占模式(Exclusive Mode)

  • 特点:同一时刻仅允许一个线程持有资源。

  • 典型应用ReentrantLockCountDownLatch

  • 核心方法

  • acquire(int arg):获取资源(失败则入队阻塞)。

  • release(int arg):释放资源(唤醒后继节点)。

acquire 流程

共享模式(Shared Mode)

  • 特点:允许多个线程同时持有资源(如读锁、信号量)。

  • 典型应用SemaphoreReentrantReadWriteLock 的读锁。

  • 核心方法

  • acquireShared(int arg):获取共享资源。

  • releaseShared(int arg):释放共享资源(可能唤醒多个后继)。

releaseShared 的传播性

scss 复制代码
private void doReleaseShared() {        if (h != null && h != tail) {            int ws = h.waitStatus;            if (ws == Node.SIGNAL) {                if (compareAndSetWaitStatus(h, Node.SIGNAL, 0))                    unparkSuccessor(h); // 唤醒后继            else if (ws == 0)                compareAndSetWaitStatus(h, 0, Node.PROPAGATE);        if (h == head) break; // 若head未变则退出

四、实际开发中如何与AQS交互

场景1:使用现成的AQS实现

普通开发不需要直接使用AQS,而是使用基于AQS的工具类:

java 复制代码
import java.util.concurrent.locks.ReentrantLock;import java.util.concurrent.CountDownLatch;import java.util.concurrent.Semaphore;public class AQSInPractice {    public static void main(String[] args) throws InterruptedException {        // 1. ReentrantLock - 可重入锁        ReentrantLock lock = new ReentrantLock(true); // true表示公平锁            System.out.println("锁已获取");            lock.unlock();        // 2. CountDownLatch - 线程协调        CountDownLatch latch = new CountDownLatch(3);        for (int i = 0; i < 3; i++) {            new Thread(() -> {                latch.countDown();        latch.await(); // 等待所有线程完成        System.out.println("所有任务完成");        // 3. Semaphore - 信号量        Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问        semaphore.acquire();            semaphore.release();

场景2:扩展AQS实现自定义同步器

可以继承AQS实现自定义同步需求:

ruby 复制代码
import java.util.concurrent.locks.AbstractQueuedSynchronizer;// 一个简单的"最多允许N个线程访问"的自定义锁public class SimpleLimitLock {    private final Sync sync;    public SimpleLimitLock(int limit) {        sync = new Sync(limit);    private static class Sync extends AbstractQueuedSynchronizer {        Sync(int limit) {            setState(limit); // 初始化状态为允许的最大线程数        protected int tryAcquireShared(int acquires) {                int available = getState();                int remaining = available - acquires;                // 如果剩余许可不足或CAS成功,返回结果                if (remaining < 0 ||                     compareAndSetState(available, remaining)) {                    return remaining;        protected boolean tryReleaseShared(int releases) {                int current = getState();                int next = current + releases;                if (compareAndSetState(current, next)) {                    return true;        sync.acquireShared(1);    public void unlock() {        sync.releaseShared(1);

五、AQS在Java并发体系中的角色

scss 复制代码
// Java并发编程的层次结构:
┌─────────────────────────────────────────────────┐
│             Java并发应用层                        │
│  • 线程池 (ExecutorService)                     │
│  • 并发集合 (ConcurrentHashMap)                 │
│  • 异步任务 (CompletableFuture)                 │
├─────────────────────────────────────────────────┤
│             AQS工具类层                          │
│  • ReentrantLock                                │
│  • Semaphore                                    │
│  • CountDownLatch                               │
│  • CyclicBarrier                                │
├─────────────────────────────────────────────────┤
│             AQS框架层                            │
│  • 状态管理 (state)                             │
│  • 队列管理 (CLH队列)                           │
│  • 线程阻塞/唤醒 (LockSupport)                  │
├─────────────────────────────────────────────────┤
│             JVM/OS层                            │
│  • synchronized监视器锁                         │
│  • CAS指令 (Unsafe类)                           │
│  • 线程调度 (操作系统)                          │
└─────────────────────────────────────────────────┘

六、AQS的工作流程示例

以ReentrantLock的加锁过程为例:

ruby 复制代码
        sync.acquire(1); // 调用AQS的模板方法    abstract static class Sync extends AbstractQueuedSynchronizer {        // AQS.acquire()的调用链:        // 1. tryAcquire() 尝试获取锁(子类实现)        // 2. addWaiter() 创建节点加入队列        // 3. acquireQueued() 在队列中自旋/阻塞        // 4. 如果被中断过,自我中断恢复状态

具体过程可以用图表示:

七、实际开发中的最佳实践

1. 选择合适的同步工具

csharp 复制代码
public class SyncToolSelection {    void useReentrantLock() {        ReentrantLock lock = new ReentrantLock(true); // 公平锁    void useSemaphore() {        Semaphore semaphore = new Semaphore(10); // 最多10个线程同时访问        // 适用于数据库连接池、限流场景    void useCountDownLatch() {        CountDownLatch latch = new CountDownLatch(5);        // 适用于启动阶段等待初始化完成    void useCyclicBarrier() {        CyclicBarrier barrier = new CyclicBarrier(4);        // 适用于并行计算,等待所有线程到达屏障

2. 避免常见陷阱

vbnet 复制代码
public class AQSPitfalls {        ReentrantLock lock = new ReentrantLock();            if (someCondition) {                return; // 这里直接返回,忘记解锁!            lock.unlock(); // 必须在finally中释放    void conditionVariableMistake() {        ReentrantLock lock = new ReentrantLock();        Condition condition = lock.newCondition();        // 错误:没有在lock保护下调用await        // condition.await();             condition.await();            lock.unlock();

八、总结:AQS在并发编程中的角色

  1. 框架提供者:为锁和同步器提供通用实现框架
  2. 基础设施:是Java并发包的基石,如大楼的地基
  3. 性能优化:相比synchronized提供更灵活的优化空间
  4. 功能扩展:支持公平/非公平、可重入、读写分离等高级特性
相关推荐
未秃头的程序猿1 小时前
从“拆东墙补西墙”到“最终一致”:分布式事务在Spring Boot/Cloud中的破局之道
分布式·后端·spring cloud
dgvri1 小时前
Spring Boot接收参数的19种方式
java·spring boot·后端
做个文艺程序员1 小时前
Function Calling 与工具调用:让 AI 真正干活【OpenClAW + Spring Boot 系列 第5篇】
人工智能·spring boot·后端
rOuN STAT2 小时前
Spring Boot 2.7.x 至 2.7.18 及更旧的版本,漏洞说明
java·spring boot·后端
我叫黑大帅2 小时前
Golang中的map的key可以是哪些类型?可以嵌套map吗?
后端·面试·go
青槿吖2 小时前
Feign 微服务远程调用指南:告别手写 RestTemplate
java·redis·后端·spring·微服务·云原生·架构
神奇小汤圆2 小时前
Linux 动态库 .so 工作原理,后端 / 嵌入式必看
后端
shy^-^cky2 小时前
RESTful 中的状态转移方法
后端·restful
枕星而眠2 小时前
C 语言结构体硬核总结:内存对齐、#pragma pack、位段、柔性数组(面试+工程双指南)
c语言·后端·面试·柔性数组