当前位置: 首页 > news >正文

AQS的原理与ReentrantLock的关系

一、什么是AQS?

AQS(AbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent)的核心基础组件,很多并发工具(如 ReentrantLock、Semaphore、CountDownLatch 等)都是基于它实现的。

AQS是一种提供了原子式管理同步阻塞和唤醒线程等功能、并提供等待队列模型的线程同步框架

简单说,AQS 的核心作用是:帮你管理多线程争抢资源的顺序

用生活例子理解 AQS 原理:

想象你去银行办理业务:

  1. 银行柜台(资源)每次只能服务一个人
  2. 所有人(线程)来了先问:"现在没人吧?我能直接办理吗?"(尝试获取资源)
  3. 如果没人(资源空闲),就直接上前办理(获取到锁)
  4. 如果有人正在办理(资源被占用),就去排队(进入等待队列)
  5. 办完业务的人离开时,会叫下一个排队的人(唤醒队列中的线程)

AQS 就像这个银行的 "排队叫号系统",它做了三件核心事:

  • 记录当前资源是否被占用(用一个 int 变量 "state" 表示,0 = 空闲,>0 = 被占用)
  • 维护一个等待队列(没抢到资源的线程排队的地方)
  • 处理线程的等待和唤醒(没抢到的线程进入队列等待,资源释放时唤醒下一个)

核心原理拆解:

  1. 状态变量(state)
    • 这是 AQS 的核心,用 volatile 修饰(保证线程可见性)
    • 不同工具对 state 的解读不同:
      • ReentrantLock 中:state=0 表示锁空闲,>0 表示重入次数
      • Semaphore 中:state 表示剩余许可证数量
  1. 等待队列(CLH 队列)
    • 本质是个双向链表,每个节点代表一个等待的线程
    • 线程没抢到资源时,会被包装成节点加入队列尾部
    • 队列遵循 "先来后到" 原则,保证基本的顺序性
  1. 核心逻辑
    • 抢资源:线程先尝试通过 CAS 操作修改 state(比如从 0→1),成功则直接占用资源
    • 排队:抢不到就进入队列,然后阻塞自己(通过 LockSupport.park ())
    • 唤醒:占用资源的线程释放时(修改 state 为 0),会从队列头唤醒一个线程,让它再次尝试抢资源

二、ReentrantLock与AQS

可以把 ReentrantLock(可重入锁)和 AQS 的关系理解为:AQS 是一套"锁的模板",ReentrantLock 是基于这个模板做的"具体产品"

打个生活比方:

把 AQS 想象成一个"智能门锁系统"的基础框架,它包含:

  • 一个"锁芯状态"(对应 AQS 的 state 变量,0 表示没锁,1 表示已锁)
  • 一个"门外排队队列"(对应 AQS 的等待队列,抢不到锁的人排队)
  • 一套"开锁/关锁/叫号"的基础逻辑(对应 AQS 的获取/释放/唤醒机制)

而 ReentrantLock 就像用这个框架做的一把具体的锁,它规定了:

  • "锁芯状态"怎么用:0 是没锁,大于 0 表示被同一个人反复锁了几次(可重入)
  • "谁能开锁":只有持有锁的人能反复开锁(重入特性)
  • "排队规则":可以选"公平模式"(按排队顺序开锁)或"非公平模式"(新来的人可以试试插队)

具体说两者的关系:

  1. ReentrantLock 依赖 AQS 实现核心功能
    ReentrantLock 内部有一个 Sync 类,这个类直接继承了 AQS。它所有关于"加锁"、"解锁"、"排队"的逻辑,其实都是通过 Sync 类调用 AQS 的方法完成的。就像你买的智能门锁(ReentrantLock),其实是用了通用的门锁框架(AQS),只是根据自己的需求做了点定制。
  2. ReentrantLock 定制了 AQS 的关键逻辑
    AQS 是个抽象类,它留了几个"钩子方法"(比如 tryAcquire、tryRelease)让子类自己实现。ReentrantLock 就是通过重写这些方法,定义了自己的规则:
    • 加锁(tryAcquire):如果锁没人用,就直接拿走;如果自己已经拿到了,就把"锁芯状态"加 1(重入);如果别人在用,就去排队。
    • 解锁(tryRelease):每次解锁就把"锁芯状态"减 1,直到减到 0 时,才算真正释放锁,然后通知队列里的下一个人。
  1. 公平/非公平锁的实现也靠 AQS
    ReentrantLock 的公平和非公平模式,其实是通过 Sync 的两个子类(FairSync 和 NonfairSync)实现的。它们的区别只在于加锁时是否遵守"排队顺序"——这个判断逻辑也是基于 AQS 的等待队列完成的。

一句话总结:

AQS 是个"万能锁架子",提供了排队、唤醒、状态管理等基础功能;ReentrantLock 是在这个架子上按"可重入锁"的需求定制的具体实现,省了自己从零写一套锁逻辑的麻烦。

就像你用乐高积木(AQS)拼了一个具体的模型(ReentrantLock),积木是通用的,但模型有自己的样子和功能。

我们来看一下 ReentrantLock 与 AQS 相关的核心源码,只保留最关键的部分以便理解:

1. ReentrantLock 的结构(依赖 AQS)

public class ReentrantLock implements Lock, java.io.Serializable {// 内部同步器,继承自 AQSprivate final Sync sync;// 抽象内部类,继承 AQSabstract static class Sync extends AbstractQueuedSynchronizer {// 加锁逻辑(留给子类实现)abstract void lock();// 非公平尝试获取锁final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();// 情况1:锁未被占用(state=0)if (c == 0) {// 直接抢锁(CAS修改state)if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current); // 标记当前线程持有锁return true;}}// 情况2:当前线程已持有锁(可重入)else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires; // 重入计数+1if (nextc < 0) // 溢出保护throw new Error("Maximum lock count exceeded");setState(nextc); // 更新statereturn true;}// 以上都不满足,抢锁失败return false;}// 释放锁protected final boolean tryRelease(int releases) {int c = getState() - releases; // 重入计数-1// 只有持有锁的线程能释放if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) { // 计数减到0,真正释放锁free = true;setExclusiveOwnerThread(null); // 清除持有线程标记}setState(c); // 更新statereturn free;}}
}

2. 非公平锁的实现(NonfairSync)

static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;// 非公平锁的加锁逻辑final void lock() {// 第一步:上来直接抢锁(插队)if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());else// 抢不到,再走AQS的排队流程acquire(1);}// 尝试获取锁(调用父类的非公平实现)protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}
}

3. 公平锁的实现(FairSync)

static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;// 公平锁的加锁逻辑:直接进入AQS排队流程,不插队final void lock() {acquire(1);}// 公平锁的尝试获取逻辑protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) { // 锁未被占用// 关键:先检查是否有线程排队(公平性体现)// hasQueuedPredecessors()是AQS的方法,判断队列中是否有比当前线程更早等待的线程if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 可重入逻辑(和非公平锁一样)else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}
}

核心逻辑总结

  1. ReentrantLock 与 AQS 的关系
    ReentrantLock 通过内部类 Sync 继承 AQS,把加锁/解锁的核心逻辑委托给 AQS 实现。
  1. 关键依赖 AQS 的部分
    • state 变量:AQS 提供的状态变量,在 ReentrantLock 中表示"重入次数"
    • compareAndSetState:AQS 提供的 CAS 方法,用于原子修改状态
    • acquire/release:AQS 提供的模板方法,处理排队、阻塞、唤醒逻辑
    • hasQueuedPredecessors:AQS 提供的方法,判断队列中是否有更早等待的线程(公平锁关键)
  1. ReentrantLock 自己实现的部分
    主要是重写 AQS 的 tryAcquire(获取锁规则)和 tryRelease(释放锁规则),定义了"可重入"特性和"公平/非公平"的抢锁规则。

简单说:AQS 提供了"排队叫号"的基础设施,ReentrantLock 则规定了"谁能拿到号"、"拿号后能续几次"的规则。

AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发编程的"基础设施",是JUC(java.util.concurrent)包中许多同步工具的底层实现框架。

可以简单理解为:AQS是一个标准化的"线程排队抢资源"框架,它提前写好了一套线程如何竞争资源、如何排队等待、如何被唤醒的通用逻辑,让开发者可以基于它快速实现各种锁和同步工具。

AQS的核心设计:"一个状态 + 一个队列 + 一套模板方法"

1. 一个状态(State)

用一个volatile int state变量表示共享资源的状态,比如:

  • 在ReentrantLock中,state表示"锁的重入次数"(0=未锁定,>0=已锁定)
  • 在Semaphore中,state表示"剩余许可证数量"
  • 在CountDownLatch中,state表示"剩余倒计时次数"

AQS提供了三个核心方法操作state:

  • getState():获取当前状态
  • setState(int newState):设置状态(不保证原子性)
  • compareAndSetState(int expect, int update):CAS方式原子更新状态
2. 一个队列(CLH队列)

当线程争抢资源失败时,会被包装成一个"节点"(Node)加入到这个队列中等待。

这个队列是个双向链表,特点是:

  • 遵循FIFO(先进先出)原则,保证基本的等待顺序
  • 每个节点包含等待的线程、节点状态(如"等待中"、"已取消")等信息
  • 线程进入队列后会被暂停(通过LockSupport.park()),直到被唤醒
3. 一套模板方法(核心流程)

AQS定义了线程获取资源和释放资源的标准流程,主要包括:

获取资源的流程

  1. 线程先尝试通过tryAcquire(int arg)方法获取资源(这个方法需要子类实现,定义具体的获取规则)
  2. 如果成功,直接继续执行
  3. 如果失败,将线程包装成节点加入等待队列
  4. 暂停当前线程(进入阻塞状态)

释放资源的流程

  1. 线程通过tryRelease(int arg)方法释放资源(这个方法也需要子类实现)
  2. 如果释放成功,从等待队列中唤醒一个或多个线程
  3. 被唤醒的线程再次尝试获取资源

AQS 数据结构

AQS 的核心数据结构包括状态变量CLH 等待队列

  1. 状态变量java运行
private volatile int state;  // 共享资源状态
    • 提供getState()setState()compareAndSetState()方法操作 state
    • 其中compareAndSetState()基于 Unsafe 类实现,保证原子性
  1. CLH 等待队列(双向链表)java运行


队列中的节点(Node)结构:

static final class Node {// 节点状态(如等待中、已取消、条件等待等)volatile int waitStatus;// 前驱节点volatile Node prev;// 后继节点volatile Node next;// 节点对应的线程volatile Thread thread;// 条件队列中的后继节点(用于Condition)Node nextWaiter;
}
    • 节点状态(waitStatus)有多种取值,如:
      • 0:初始状态
      • -1(SIGNAL):后继节点需要被唤醒
      • 1(CANCELLED):节点已取消等待
      • -2(CONDITION):节点在条件队列中
      • -3(PROPAGATE):共享模式下的传播状态

举例:

  1. 线程 A 来抢锁,发现 state=0(没人用),用 CAS 把 state 改成 1,直接办理业务
  2. 线程 B 来了,state=1(被占用),变成 Node 节点加入链表尾部,然后休眠
  3. 线程 C 来了,同样变成 Node 节点加到 B 后面,休眠
  4. 线程 A 办完业务,把 state 改回 0,然后看链表有节点,就唤醒线程 B
  5. 线程 B 被唤醒,用 CAS 把 state 改成 1,开始办理业务,链表头移到 B(C 变成第二个)

总结

AQS 的数据结构其实很简单:

  • 计数器(state):记录资源是否被占用、被占用多少次
  • 等待链表:让抢不到资源的线程按顺序排队,避免混乱

这两个结构配合,就实现了 "线程抢资源 - 排队 - 唤醒" 的完整逻辑,成为所有 Java 同步工具的基础。

开发者如何使用AQS?

AQS是抽象类,不能直接使用,需要继承AQS并重写关键方法

  • tryAcquire(int arg):定义获取资源的规则
  • tryRelease(int arg):定义释放资源的规则
  • 还有针对共享模式的tryAcquireSharedtryReleaseShared

其他复杂逻辑(如排队、阻塞、唤醒)AQS已经帮我们实现好了,不用重复开发。

为什么AQS如此重要?

没有AQS时,每个同步工具(如锁、信号量)都要自己实现一套线程排队、竞争、唤醒的逻辑,不仅麻烦还容易出错。

AQS相当于把这些通用逻辑"标准化",让开发者可以专注于具体同步工具的特有规则(比如锁的可重入性、信号量的许可证数量等)。

Java中很多并发工具都是基于AQS实现的,例如:

  • ReentrantLock(可重入锁)
  • Semaphore(信号量)
  • CountDownLatch(倒计时器)
  • ReentrantReadWriteLock(读写锁)
  • ThreadPoolExecutor中的工作队列控制

理解AQS,就理解了Java并发工具的"内功心法"。

http://www.dtcms.com/a/328641.html

相关文章:

  • 基于Rocky Linux 9的容器化部署方案,使用Alpine镜像实现轻量化
  • 企业高性能web服务器(3)
  • Linux学习-应用软件编程(文件IO)
  • 【科研绘图系列】R语言绘制特定区域颜色标记散点图
  • Pytest项目_day13(usefixture方法、params、ids)
  • 【不动依赖】Kali Linux 2025.2 中安装mongosh
  • 【数据结构】二叉树详细解析
  • 安路Anlogic FPGA下载器的驱动安装与测试教程
  • C++联合体的定义
  • 数据结构 二叉树(2)堆
  • 带宽受限信道下的数据传输速率计算:有噪声与无噪声场景
  • C++方向知识汇总(四)
  • PyCATIA高级建模技术:等距平面、点云重命名与棱柱体创建的工业级实现
  • 基于Java与Vue搭建的供应商询报价管理系统,实现询价、报价、比价全流程管理,功能完备,提供完整可运行源码
  • Python训练营打卡Day30-文件的规范拆分和写法
  • 树与二叉树
  • NY198NY203美光固态闪存NY215NY216
  • 串口通信学习
  • Xshell远程连接Ubuntu 24.04.2 LTS虚拟机
  • 模型 霍特林法则
  • 自动驾驶 HIL 测试:构建 “以假乱真” 的实时数据注入系统
  • 【JavaEE】多线程之线程安全(上)
  • 学习嵌入式的第十八天——Linux——文件编程
  • nexus-集成prometheus监控指标
  • 力扣面试150题--爬楼梯 打家劫舍 零钱兑换 最长递增子序列
  • DDD之工程结构(7)
  • 数据库规范化:消除冗余与异常的核心法则
  • 用 Spring 思维快速上手 DDD——以 Kratos 为例的分层解读
  • 当赞美来敲门:优雅接纳的艺术
  • 在线免VIP的动漫网站