AQS 概念

AbstractQueuedSynchronizer(AQS) 是 Java 并发包 (java.util.concurrent.locks) 的核心基础框架,它的实现关键是先进先出 (FIFO) 等待队列和一个用volatile修饰的锁状态status。具体实现有 : ReentrantLockSemaphoreCountDownLatchReentrantReadWriteLock 等常用的同步工具类。

AQS 的核心思想

AQS 的核心思想是 : 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁 实现的,即将暂时获取不到锁的线程加入到队列中。

CLH 队列:Craig, Landin, and Hagersten (CLH) locks,是一种基于链表的可扩展、高性能、公平的自旋锁。AQS 中的队列是 CLH 变体的虚拟双向队列(FIFO)。

AQS 的核心架构

在这里插入图片描述

  • AQS 使用一个 volatile int state 成员变量来表示同步状态
  • 通过内置的 FIFO 队列 来完成资源获取线程的排队工作。
  • Node中的thread变量用来存放进入AQS队列里面的线程,Node节点内部:
    • prev记录当前节点的前驱节点
    • next 记录当前节点的后继节点
  • SHARED用来标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的
  • EXCLUSIVE用来标记线程是获取独占资源时被挂起后放入AQS队列的
  • waitStatus 记录当前线程等待状态,可以为
    • CANCELLED (线程被取消了)
    • SIGNAL(线程需要被唤醒)
    • CONDITION(线程在CONDITION条件队列里面等待)
    • PROPAGATE(释放共享资源时需要要通知其他节点);

图中源码解释 :

static final int WAITING   = 1;          // 等待状态,必须为1
static final int CANCELLED = 0x80000000; // 取消状态,必须为负数(最高位为1)
static final int COND      = 2;          // 在条件等待中/** CLH 节点 */
abstract static class Node {volatile Node prev;       // 前驱节点,最初通过 casTail 附加volatile Node next;       // 当可发出信号时 visibly nonnullThread waiter;            // 当入队时 visibly nonnullvolatile int status;      // 由所有者写入,其他线程通过原子位操作读取// 用于 cleanQueue 的比较并交换前驱节点final boolean casPrev(Node c, Node v) {return U.weakCompareAndSetReference(this, PREV, c, v);}// 用于 cleanQueue 的比较并交换后继节点final boolean casNext(Node c, Node v) {return U.weakCompareAndSetReference(this, NEXT, c, v);}// 用于信号传递的获取并清除状态位final int getAndUnsetStatus(int v) {return U.getAndBitwiseAndInt(this, STATUS, ~v);}// 用于离队赋值的宽松设置前驱节点final void setPrevRelaxed(Node p) {U.putReference(this, PREV, p);}// 用于离队赋值的宽松设置状态final void setStatusRelaxed(int s) {U.putInt(this, STATUS, s);}// 用于减少不必要信号的状态清除final void clearStatus() {U.putIntOpaque(this, STATUS, 0);}// 内存偏移量常量private static final long STATUS = U.objectFieldOffset(Node.class, "status");private static final long NEXT = U.objectFieldOffset(Node.class, "next");private static final long PREV = U.objectFieldOffset(Node.class, "prev");
}// 按类型标记的具体节点类
static final class ExclusiveNode extends Node { }  // 独占模式节点
static final class SharedNode extends Node { }     // 共享模式节点// 条件节点,实现 ManagedBlocker 接口用于 ForkJoinPool
static final class ConditionNode extends Node implements ForkJoinPool.ManagedBlocker {ConditionNode nextWaiter; // 链接到下一个等待节点/*** 允许条件在 ForkJoinPools 中使用,而不会冒险耗尽固定池。* 这仅适用于非定时条件等待,不适用于定时版本。*/public final boolean isReleasable() {return status <= 1 || Thread.currentThread().isInterrupted();}public final boolean block() {while (!isReleasable()) LockSupport.park();return true;}
}// 等待队列头节点,延迟初始化
private transient volatile Node head;// 等待队列尾节点。初始化后,仅通过 casTail 修改
private transient volatile Node tail;// 同步状态
private volatile int state;/*** 返回同步状态的当前值。* @return 当前状态值*/
protected final int getState() {return state;
}

AQS 底层数据结构

state

state 是 AQS 的核心字段,表示共享资源的状态。不同的同步器对 state 的解释不同:

  • ReentrantLockstate 表示当前线程获取锁的可重入次数(如果发现可以可重入,则state+1; 执行完成则-1; 为0时可以释放)
  • Semaphorestate 表示当前可用信号的个数
  • CountDownLatchstate 表示计数器当前的值

AQS 提供了一系列访问 state 的方法:

// 获取当前同步状态
protected final int getState() {return state;
}// 设置同步状态
protected final void setState(int newState) {state = newState;
}// 使用CAS原子性地设置同步状态
protected final boolean compareAndSetState(int expect, int update) {return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

同步队列:Node 节点

AQS 内部维护了一个 FIFO 双向队列,队列中的每个元素都是一个 Node 对象:

static final class Node {// 节点模式:共享模式static final Node SHARED = new Node();// 节点模式:独占模式static final Node EXCLUSIVE = null;// 等待状态:线程已取消static final int CANCELLED =  1;// 等待状态:后继节点的线程需要被唤醒static final int SIGNAL    = -1;// 等待状态:节点在条件队列中等待static final int CONDITION = -2;// 等待状态:下一次acquireShared应无条件传播static final int PROPAGATE = -3;// 当前节点的等待状态volatile int waitStatus;// 前驱节点volatile Node prev;// 后继节点volatile Node next;// 节点关联的线程volatile Thread thread;// 指向下一个等待条件或共享模式的节点Node nextWaiter;// 判断节点是否在共享模式下等待final boolean isShared() {return nextWaiter == SHARED;}// 获取前驱节点final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}// 构造方法Node() {    // Used to establish initial head or SHARED marker}Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}Node(Thread thread, int waitStatus) { // Used by Conditionthis.waitStatus = waitStatus;this.thread = thread;}
}

AQS 的整体结构

AQS 的整体结构如下图所示:

在这里插入图片描述

线程获取锁失败时的 AQS 队列变化

初始状态

当没有线程竞争锁时,AQS 的同步队列处于初始状态,headtail 都为 null

// 初始状态
head = null;
tail = null;

第一个线程获取锁失败

当第一个线程尝试获取锁失败时,AQS 会初始化同步队列:

  1. 创建空节点(dummy node)作为头节点
  2. 将当前线程包装成 Node 节点添加到队列尾部
// addWaiter 方法:将当前线程包装成Node并加入队列
private Node addWaiter(Node mode) {// 将当前线程包装成Node节点Node node = new Node(Thread.currentThread(), mode);// 尝试快速入队Node pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 快速入队失败,使用enq方法自旋入队enq(node);return node;
}// enq方法:通过自旋CAS确保节点成功入队
private Node enq(final Node node) {for (;;) {Node t = tail;if (t == null) { // 队列未初始化// 创建空节点(dummy node)作为头节点if (compareAndSetHead(new Node()))tail = head; // 头尾都指向空节点} else {// 将新节点添加到队列尾部node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}

初始化后的队列结构:

head → [dummy node] ← tail↑新加入的节点

后续线程获取锁失败

当后续线程尝试获取锁失败时,它们会被添加到队列的尾部:

// acquireQueued方法:线程在队列中自旋获取资源或阻塞
final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 获取前驱节点final Node p = node.predecessor();// 如果前驱是头节点,尝试获取资源if (p == head && tryAcquire(arg)) {// 获取成功,设置当前节点为头节点setHead(node);p.next = null; // help GC (方便垃圾回收)failed = false;return interrupted;}// 判断是否应该阻塞,并在需要时安全地阻塞if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

多个线程竞争后的队列结构 :
在这里插入图片描述

线程被唤醒时的 AQS 队列变化

锁释放与线程唤醒

当持有锁的线程释放资源时,会唤醒队列中的下一个线程:

// release方法:释放独占模式下的资源
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h); // 唤醒后继节点return true;}return false;
}// unparkSuccessor方法:唤醒指定节点的后继节点
private void unparkSuccessor(Node node) {int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0); // 清除状态// 查找下一个需要唤醒的节点Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;// 从尾部向前查找,找到队列中最前面且未取消的节点for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)LockSupport.unpark(s.thread); // 唤醒线程
}

4.2 线程被唤醒后的队列变化

当队列中的线程被唤醒后,它会尝试获取锁,如果获取成功,会将自己设置为新的头节点:

// setHead方法:将节点设置为头节点
private void setHead(Node node) {head = node;node.thread = null; // 清空线程引用node.prev = null;
}

原来的头节点(dummy node)会被垃圾回收,新头节点的 thread 字段被设置为 null,表示它不再关联任何线程。

公平锁与非公平锁

AQS 支持两种锁获取模式:公平锁非公平锁

非公平锁(如 ReentrantLock 的默认实现):

final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 不管队列中是否有等待线程,直接尝试获取锁if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 重入逻辑...
}

公平锁

protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 只有队列中没有等待线程或当前线程是队列中的第一个时才获取锁if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}// 重入逻辑...
}

hasQueuedPredecessors() 方法检查队列中是否有比当前线程等待时间更长的线程,这是实现公平性的关键。

AQS 的设计模式

模板方法模式

AQS 使用了模板方法模式,定义了同步器的主要逻辑骨架,而将一些特定操作留给子类实现。以下是需要子类实现的方法:

方法名描述
boolean tryAcquire(int arg)尝试以独占方式获取资源
boolean tryRelease(int arg)尝试释放独占资源
int tryAcquireShared(int arg)尝试以共享方式获取资源
boolean tryReleaseShared(int arg)尝试释放共享资源
boolean isHeldExclusively()判断当前线程是否独占资源

以 ReentrantLock 为例的实现

ReentrantLock 中的同步器 Sync 继承自 AQS,并实现了相关方法:

abstract static class Sync extends AbstractQueuedSynchronizer {// 尝试获取锁abstract void lock();// 非公平尝试获取final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}// 尝试释放锁protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}// 判断当前线程是否独占资源protected final boolean isHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread();}
}

优势

  1. 代码复用:AQS 提供了通用的同步框架,多个同步器可以复用相同的队列管理逻辑
  2. 职责分离:AQS 负责线程管理,子类专注于状态管理

装饰器模式

装饰器模式动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。

在 AQS 中的实现

AQS 中的 Node 类可以看作是对 Thread 的装饰,它添加了同步所需的额外信息:

static final class Node {// 节点模式:共享或独占volatile Node nextWaiter;// 等待状态volatile int waitStatus;// 前驱和后继指针volatile Node prev;volatile Node next;// 被装饰的线程对象volatile Thread thread;// 构造方法 - 装饰线程Node(Thread thread, Node mode) {     // Used by addWaiterthis.nextWaiter = mode;this.thread = thread;}
}

优势

  1. 功能扩展:在不修改 Thread 类的情况下为其添加同步功能
  2. 灵活性:可以根据需要为线程添加不同的同步属性

状态模式

状态模式允许一个对象在其内部状态改变时改变它的行为。

在 AQS 中的实现

AQS 中节点的 waitStatus 字段代表了不同的状态,每种状态对应不同的行为:

static final class Node {// 状态常量static final int CANCELLED =  1;  // 取消状态static final int SIGNAL    = -1;  // 需要唤醒后继节点static final int CONDITION = -2;  // 在条件队列中等待static final int PROPAGATE = -3;  // 传播唤醒操作// 状态字段volatile int waitStatus;
}

根据状态的不同,AQS 采取不同的处理策略:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws = pred.waitStatus;if (ws == Node.SIGNAL) // 根据状态决定行为return true;if (ws > 0) { // 处理取消状态do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}

AQS 的使用

ReentrantLock 的实现

ReentrantLock 通过内部类 Sync 继承 AQS,实现了可重入的独占锁:

public class ReentrantLock implements Lock, java.io.Serializable {private final Sync sync;abstract static class Sync extends AbstractQueuedSynchronizer {}// 非公平锁实现static final class NonfairSync extends Sync {final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}// 公平锁实现static final class FairSync extends Sync {final void lock() {acquire(1);}protected final boolean tryAcquire(int acquires) {// 公平获取逻辑}}
}

CountDownLatch 的实现

CountDownLatch 通过内部类 Sync 继承 AQS,实现了共享模式的同步器:

public class CountDownLatch {private static final class Sync extends AbstractQueuedSynchronizer {Sync(int count) {setState(count);}int getCount() {return getState();}protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {for (;;) {int c = getState();if (c == 0)return false;int nextc = c - 1;if (compareAndSetState(c, nextc))return nextc == 0;}}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/98824.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/98824.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/98824.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Dart → `.exe`:Flutter 桌面与纯命令行双轨编译完全指南

Dart → .exe&#xff1a;Flutter 桌面与纯命令行双轨编译完全指南 关键词&#xff1a;Dart、Flutter、Windows、可执行文件、桌面端、CLI、交叉编译 1. 前言 很多开发者以为 Dart 只能跑在 AOT 移动端或 Web 端&#xff0c;其实 官方工具链早已支持一键输出 Windows 原生 .ex…

互联网接入网中PPPoE和PPP协议

<摘要> PPPoE和PPP是宽带接入网络中至关重要的协议组合&#xff0c;其中PPP提供通用的点对点链路层解决方案&#xff0c;而PPPoE则是在以太网架构上扩展PPP应用的技术桥梁。本文从技术演进视角系统解析了两者的内在关联与本质区别&#xff1a;PPP作为成熟链路层协议&…

详细解析SparkStreaming和Kafka集成的两种方式的区别和优劣

spark streaming是基于微批处理的流式计算引擎&#xff0c;通常是利用spark core或者spark core与spark sql一起来处理数据。在企业实时处理架构中&#xff0c;通常将spark streaming和kafka集成作为整个大数据处理架构的核心环节之一。 针对不同的spark、kafka版本&#xff0…

Kite Compositor for Mac v2.1.2 安装教程|DMG文件安装步骤(Mac用户必看)

Kite Compositor​ 是一款专为 ​macOS​ 设计的 ​轻量级界面设计 & 动画制作工具&#xff0c;它可以让你像拼图一样直观地 ​创建、编辑和预览用户界面&#xff08;UI&#xff09;以及动画效果。 一、下载文件 首先&#xff0c;你得先把这个 ​Kite Compositor for Mac …

【逆向】Android程序静态+动态分析——去壳

对提供的 CrackmeTest.apk 进行逆向分析&#xff0c;程序含有反调试机制&#xff08;加壳&#xff09;&#xff0c;通过静态补丁反反调试&#xff08;去壳&#xff09;&#xff0c;再动态调试获取其中密码。 目录 环境 基础 实验内容 静态分析 动态分析 反反调试 再动态…

Rust 开发环境安装与 crates.io 国内源配置(Windows / macOS / Linux 全流程)

Rust 这几年在系统编程、WebAssembly、区块链、后端服务领域越来越火&#xff0c;很多开发者都在尝试用它做一些新项目。 但是国内安装 Rust 开发环境时&#xff0c;经常遇到 安装慢、依赖拉不下来、crates.io 超时 等问题。本文结合个人踩坑经验&#xff0c;整理了一份 跨平台…

Nginx SSL/TLS 配置

Nginx SSL/TLS 配置指南&#xff1a;从入门到安全强化前言一、环境准备&#xff1a;Nginx安装配置1.1. **EPEL仓库配置**&#xff1a;1.2. **Nginx安装**&#xff1a;1.3. **服务启停管理**&#xff1a;1.4. **服务状态验证**&#xff1a;二、SSL/TLS证书获取方案方案A&#xf…

Java ReentrantLock和synchronized的相同点与区别

1. 核心概念与定位synchronized&#xff1a;Java 内置的关键字&#xff0c;属于 JVM 层面的隐式锁。通过在方法或代码块上声明&#xff0c;自动实现锁的获取与释放&#xff0c;无需手动操作。设计目标是提供简单易用的基础同步能力&#xff0c;适合大多数常规同步场景。Reentra…

【npm】npm 包更新工具 npm-check-updates (ncu)

npm 包太多了&#xff0c;一个项目有那么多依赖包&#xff0c;它们的升级管理需要一个工具&#xff1a;npm-check-updates&#xff1a; 安装&#xff1a; npm install -g npm-check-updates安装之后&#xff0c;就可以使用它的命令&#xff1a;ncu 查看哪些包可以升级&#xff…

go资深之路笔记(一) Context

一、 Context 的正确使用与底层原理 1.结构体 type Context interface {// Deadline 返回此 Context 被取消的时间点。// 如果未设置截止时间&#xff0c;ok 为 false。Deadline() (deadline time.Time, ok bool)// Done 返回一个 channel。当 Context 被取消或超时后&#xff…

VS2022 + Qt5.9 中文乱码/项目设置utf-8编码

&#x1f6e0;️ 解决QT5.9 VS2022中文乱码的全面方案 &#x1f4c1; 1. 检查文件编码与编译器设置 确保源文件是 带BOM的UTF-8 编码对MSVC编译器很重要。VS2022默认可能使用本地编码&#xff08;如GB2312&#xff09;解析源文件&#xff0c;即使文件以UTF-8保存。 查看和设置…

数据库--MySQL数据管理

数据库–MySQL数据管理 文章目录数据库--MySQL数据管理1.外键管理2.数据库数据管理3.DML语言3.1添加数据3.2修改数据3.3删除数据4.练习1.外键管理 外键概念 如果公共关键字在一个关系中是主关键字&#xff0c;那么这个公共关键字被称为另一个关系的外键。由此可见&#xff0c;…

【C++练习】13.C++输出九九乘法表的方法详解

目录 C++输出九九乘法表的方法详解 方法1:双重for循环(最基础) 思考: 代码分析: 特点: 方法2:使用while循环 思考: 代码分析: 特点: 方法3:使用递归实现 思考: 代码分析: 特点: 方法4:格式化输出(对齐美观) 思考: 代码分析: 特点: 方法5:使用函数封装 思考…

MVC及其衍生

MVC 把软件分成模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;、控制器&#xff08;Controller&#xff09;三个基本部分。 事实上对应着 Controller——输入 用户交互&#xff0c;将输入处理成Controller能处理的形式 Model——处理 描述状态、逻辑规律…

微硕WINSOK MOS管WSF3089,赋能汽车转向系统安全升级

随着汽车电子化程度不断提高&#xff0c;转向系统对高效功率器件的需求日益增长。微硕WINSOK推出的N沟道Trench MOS管WSF3089&#xff0c;以30 V/72 A大电流、4.5 mΩ超低导通电阻和TO-252-2L紧凑封装&#xff0c;为EPS&#xff08;电动助力转向&#xff09;电机驱动、电源管理…

淘宝拍立淘接口的接入与应用||item_search_img-按图搜索淘宝商品(拍立淘)

淘宝拍立淘接口的接入与应用如下&#xff1a;接入流程注册与认证&#xff1a;开发者账号注册&#xff1a;访问淘宝开放平台&#xff0c;进行开发者账号注册。创建应用&#xff1a;在控制台创建新应用&#xff0c;获取 App Key 和 App Secret&#xff0c;这是接口调用的凭证。申…

Python学习-day8 元组tuple

元组&#xff08;Tuple&#xff09;是Python中一种不可变的序列类型&#xff0c;用于存储多个有序元素。与列表&#xff08;List&#xff09;类似&#xff0c;但元组一旦创建后不能修改&#xff08;不可添加、删除或修改元素&#xff09;&#xff0c;这使得它在安全性、性能优化…

大数据毕业设计选题推荐-基于大数据的国家医用消耗选品采集数据可视化分析系统-Hadoop-Spark-数据可视化-BigData

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、PHP、.NET、Node.js、GO、微信小程序、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇…

二次学习C语言补充2

文章目录表栈、队列、二叉树一、二叉树二、表栈三、队列链表一、单向链表二、循环链表、双向链表和双向循环链表预处理一、预处理二、宏定义文件文件操作补充本篇文章是对二次学习C语言12——文件操作 二次学习C语言14——预处理及模块化 二次学习C语言15——链表 二次学习C语言…

2.9Vue创建项目(组件)的补充

1.再创建和引入vue的选择2.VsCode插件 安装Vue自己搜索最新的3.style自己的作用域在一个组件中引入另一个文件的子组件&#xff0c;给当前组件设置样式&#xff0c;那么子组件的样式也会改变的。为了解决这个问题 我们在自己的style中设置一个属性4.另一种创建vue 的方式(主流…