Java中的AQS入门攻略

 更新时间:2025年10月22日 11:17:26   作者:程序员小假  
AQS(AbstractQueuedSynchronizer)是JUC包中用于构建锁和同步器的核心框架,它通过volatile的state变量和FIFO队列实现线程同步,简化了同步工具的开发,并支持独占与共享两种模式,本文介绍Java中的AQS是什么,感兴趣的朋友跟随小编一起看看吧

一、AQS 是什么?

AQS,全称 AbstractQueuedSynchronizer,即抽象队列同步器

  • 抽象:它是一个抽象类,本身不能直接实例化,需要子类去继承它,并实现其保护方法来管理同步状态。
  • 队列:它内部维护了一个先进先出(FIFO)的等待队列,用于存放那些没有抢到锁的线程。
  • 同步器:它是构建锁和其他同步组件(如 Semaphore、CountDownLatch 等)的基础框架

核心思想:
AQS 使用一个整型的 volatile 变量(state 来表示同步状态(例如,锁被重入的次数、许可的数量等),并通过一个内置的 FIFO 队列来完成资源获取线程的排队工作。

设计模式:
AQS 是 模板方法模式 的经典应用。父类(AQS)定义了骨架和核心算法,而将一些关键的操作以 protected 方法的形式留给子类去实现。这样,实现一个自定义同步器只需要关注如何管理 state 状态即可,至于线程的排队、等待、唤醒等复杂操作,AQS 已经帮我们完成了。

二、AQS 的核心结构

AQS 的核心可以概括为三部分:同步状态(state)等待队列 和 条件队列

1. 同步状态(State)

这是一个 volatile int 类型的变量,是 AQS 的灵魂。

private volatile int state;

它的具体含义由子类决定,非常灵活:

  • 在 ReentrantLock 中,state 表示锁被同一个线程重复获取的次数。state=0 表示锁空闲,state=1 表示锁被占用,state>1 表示锁被重入。
  • 在 Semaphore 中,state 表示当前可用的许可数量。
  • 在 CountDownLatch 中,state 表示计数器当前的值。

对 state 的操作是原子的,通过 getState()setState(int newState)compareAndSetState(int expect, int update) 等方法进行。

2. 等待队列(CLH 队列的变体)

这是一个双向链表,是 AQS 实现阻塞锁的关键。当线程请求共享资源失败时,AQS 会将当前线程以及等待状态等信息构造成一个节点(Node) 并将其加入队列的尾部,同时阻塞该线程。

  • 头节点(Head):指向获取到资源的线程所在的节点。头节点不持有线程,是一个“虚节点”。
  • 尾节点(Tail):指向队列中最后一个节点。

当一个线程释放资源时,它会唤醒后继节点,后继节点成功获取资源后,会将自己设置为新的头节点。

主要原理图如下:

AQS 使用一个 Volatile 的 int 类型的成员变量来表示同步状态,通过内置的 FIFO 队列来完成资源获取的排队工作,通过 CAS 完成对 State 值的修改。

3. 条件队列(Condition Object)

AQS 内部类 ConditionObject 实现了 Condition 接口,用于支持 await/signal 模式的线程间协作。每个 ConditionObject 对象都维护了一个自己的单向链表(条件队列)

  • 当线程调用 Condition.await() 时,会释放锁,并将当前线程构造成节点加入条件队列,然后阻塞。
  • 当线程调用 Condition.signal() 时,会将条件队列中的第一个等待节点转移到 AQS 的等待队列中,等待重新获取锁。

注意:一个 AQS 实例可以对应多个 Condition 对象(即多个条件队列),但只有一个等待队列。

三、AQS 的设计与关键方法

AQS 将资源获取的方式分为两种:

  • 独占模式(Exclusive):一次只有一个线程能执行,如 ReentrantLock。
  • 共享模式(Shared):多个线程可以同时执行,如 Semaphore、CountDownLatch。

AQS 提供了顶层的入队和出队逻辑,而将尝试获取资源尝试释放资源的具体策略留给了子类。

需要子类重写的关键方法(Protected)

这些方法在 AQS 中是 protected 的,默认抛出 UnsupportedOperationException

独占模式:

  • boolean tryAcquire(int arg):尝试以独占方式获取资源。成功返回 true,失败返回 false。
  • boolean tryRelease(int arg):尝试以独占方式释放资源。成功返回 true,失败返回 false。

共享模式:

  • int tryAcquireShared(int arg):尝试以共享方式获取资源。负数表示失败;0 表示成功,但后续共享获取可能失败;正数表示成功,且后续共享获取可能成功。
  • boolean tryReleaseShared(int arg):尝试以共享方式释放资源。

其他:

  • boolean isHeldExclusively():当前同步器是否在独占模式下被线程占用。在 Condition 相关操作中会用到。

供外部调用的重要方法(Public)

这些是模板方法,子类一般不重写,使用者(或子类)直接调用。

独占模式:

  • void acquire(int arg):以独占模式获取资源,忽略中断。如果获取失败,会进入等待队列。
  • void acquireInterruptibly(int arg):同上,但响应中断。
  • boolean tryAcquireNanos(int arg, long nanosTimeout):在 acquireInterruptibly 基础上增加了超时限制。
  • boolean release(int arg):以独占模式释放资源。

共享模式:

  • void acquireShared(int arg):以共享模式获取资源。
  • void acquireSharedInterruptibly(int arg):响应中断的共享获取。
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout):带超时的共享获取。
  • boolean releaseShared(int arg):以共享模式释放资源。

四、源码级工作流程解析(以acquire为例)

我们来看一下最核心的 acquire 方法,它展示了 AQS 的完整工作流程:

public final void acquire(int arg) {
    if (!tryAcquire(arg) && // 1. 尝试直接获取资源(子类实现)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 2. 获取失败,则加入队列;3. 在队列中自旋/阻塞等待
        selfInterrupt(); // 如果在等待过程中被中断,补上中断标记
}
  • tryAcquire(arg)
    • 这是子类实现的方法。比如在 ReentrantLock 的非公平锁实现中,它会直接尝试使用 CAS 修改 state,如果成功,就将当前线程设置为独占线程。
    • 如果 tryAcquire 成功,整个 acquire 方法就结束了,线程继续执行。
    • 如果失败,进入下一步。
  • addWaiter(Node.EXCLUSIVE)
    • 创建一个代表当前线程的 Node 节点,模式为独占模式(Node.EXCLUSIVE)
    • 通过 CAS 操作,快速地将这个新节点设置为尾节点。如果失败,则进入 enq(node) 方法,通过自旋 CAS 的方式确保节点被成功添加到队列尾部。
  • acquireQueued(final Node node, int arg)
    • 这是核心中的核心。节点入队后,会在这个方法里进行自旋(循环)等待。
    • 在循环中,它会检查自己的前驱节点是不是头节点(p == head)。如果是,说明自己是队列中第一个等待的线程,会再次调用 tryAcquire 尝试获取资源(因为此时锁可能刚好被释放了,这是一个避免不必要的线程挂起、提高性能的优化)。
    • 如果获取成功,就将自己设为新的头节点,然后返回。
    • 如果前驱不是头节点,或者再次尝试获取失败,则会调用 shouldParkAfterFailedAcquire 方法,检查并更新前驱节点的状态(比如将其 waitStatus 设置为 SIGNAL,表示“当你释放锁时,需要唤醒我”)。
    • 如果一切就绪,就调用 parkAndCheckInterrupt() 方法,使用 LockSupport.park(this) 阻塞(挂起)当前线程
    • 当线程被唤醒后(通常是由前驱节点释放锁时 unpark 的),会再次检查自己是否是头节点的后继,并重复上述自旋过程,直到成功获取资源。
  • selfInterrupt()
    • 如果在等待过程中线程被中断,acquireQueued 方法会返回 true,这里会调用 selfInterrupt 补上中断标志,因为 AQS 在 acquire 过程中是忽略中断的。

释放流程(release)相对简单:

public final boolean release(int arg) {
    if (tryRelease(arg)) { // 1. 子类尝试释放资源
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 2. 唤醒后继节点
        return true;
    }
    return false;
}

unparkSuccessor 会找到队列中第一个需要唤醒的线程(通常是头节点的下一个有效节点),然后调用 LockSupport.unpark(s.thread) 将其唤醒。

五、AQS 的应用举例

AQS 是 JUC 包的基石,几乎所有的同步工具都基于它:

  • ReentrantLock:使用 AQS 的独占模式,state 表示重入次数。
  • ReentrantReadWriteLock:读写锁。AQS 的 state 高16位表示读锁状态,低16位表示写锁状态。
  • Semaphore:使用 AQS 的共享模式,state 表示可用许可数。
  • CountDownLatch:使用 AQS 的共享模式,state 表示计数器值。countDown() 是 releaseSharedawait() 是 acquireShared
  • ThreadPoolExecutor:其内部的工作线程 Worker 类,也继承了 AQS,用于实现独占锁,来判断线程是否空闲。

六、总结

AQS 的核心贡献在于,它提供了一个强大的框架,将复杂的线程排队、阻塞、唤醒等底层操作封装起来,让同步器的开发者只需要关注一个核心问题:如何管理那个 state 变量。

它的优点:

  • 极大地降低了构建锁和同步器的复杂度
  • 性能高效:通过自旋、CAS 等无锁编程技术,减少了线程上下文切换的开销。
  • 灵活强大:通过两种模式的区分,可以构建出各种复杂的同步工具。

到此这篇关于Java中的AQS入门攻略的文章就介绍到这了,更多相关java aqs是什么内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java 线程池_动力节点Java学院整理

    Java 线程池_动力节点Java学院整理

    系统启动一个新线程的成本是比较高的,因为它涉及到与操作系统的交互。在这种情况下,使用线程池可以很好的提供性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池
    2017-05-05
  • Springboot引入多个yml方法(多种方案)

    Springboot引入多个yml方法(多种方案)

    SpringBoot默认加载的是application.yml文件,所以想要引入其他配置的yml文件,就要在application.yml中激活该文件这篇文章主要介绍了Springboot引入多个yml方法,需要的朋友可以参考下
    2019-10-10
  • IDEA中用maven连接数据库的教程

    IDEA中用maven连接数据库的教程

    这篇文章主要介绍了IDEA中用maven连接数据库的教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • java  常见位逻辑运算符梳理

    java  常见位逻辑运算符梳理

    这篇文章主要介绍了java常见位逻辑运算符梳理,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参一下下面文章详细内容
    2022-08-08
  • 深入了解Java SpringBoot自动装配原理

    深入了解Java SpringBoot自动装配原理

    在使用springboot时,很多配置我们都没有做,都是springboot在帮我们完成,这很大一部分归功于springboot自动装配。本文将详细为大家讲解SpringBoot的自动装配原理,需要的可以参考一下
    2022-03-03
  • Java Volatile关键字同步机制详解

    Java Volatile关键字同步机制详解

    这篇文章主要介绍了Java Volatile关键字同步机制详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • 详解Java线程-守护线程与用户线程

    详解Java线程-守护线程与用户线程

    这篇文章主要介绍了Java守护线程与用户线程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • Hibernate命名策略详解

    Hibernate命名策略详解

    本文主要介绍了Hibernate命名策略。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • Java中RocketMQ的流量削峰详解

    Java中RocketMQ的流量削峰详解

    这篇文章主要介绍了Java中RocketMQ的流量削峰详解,MQ的主要特点为解耦、异步、削峰,该文章主要记录与分享个人在实际项目中的RocketMQ削峰用法,用于减少数据库压力的业务场景,需要的朋友可以参考下
    2023-09-09
  • 一篇文章弄懂Mybatis中#和$的区别

    一篇文章弄懂Mybatis中#和$的区别

    mybatis为我们提供了两种支持动态sql的语法#{}以及${},两者都是动态的向sql语句中传入需要的参数,下面这篇文章主要给大家介绍了如何通过一篇文章弄懂Mybatis中#和$区别的相关资料,需要的朋友可以参考下
    2021-12-12

最新评论