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

AQS源码分析

前言

AbstractQueuedSynchronizer是抽象同步队列,其是实现同步机器的基础组件,并发包中的锁的底层就是使用AQS实现的。

AQS中 维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
这里volatile能够保证多线程下的可见性,当state=1则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的等待队列中,并且会被UNSAFE.park()操作挂起,等待其他获取锁的线程释放锁才能够被唤醒。
另外state的操作都是通过CAS来保证其并发修改的安全性。

一、AQS中的关键成员变量

  • state
    • 在AQS中,维护了一个单一变量state,对于不同的实现其有不同的意义:
    • 在ReentrantLock中,state表示重入式锁的可重入次数
    • 在ReentrantReadWriteLock中,state的高16位用于表示读锁的可获取次数,低16位用于表示写锁的可重入次数。
  • exclusiveOwnerThread
    • 继承自AbstractOwnableSynchronizer,用于指明当前独占线程。
  • headtail
    • 维护了一个队列,分别指向首尾节点
  • Node
    • Node节点内部的SHARED用来标记该线程是在获取共享资源时被阻塞挂起放入AQS队列的,EXCLUSIVE用来标识该线程是获取独占资源时被阻塞挂起放入AQS队列的。
    • 在Node节点内部有一个成员变量waitStatus记录当前线程等待状态,可以为:
      • 1:CANCELLED(线程被取消了)
      • -1:SIGNAL(线程需要唤醒)
      • -2:CONDITION(线程在条件队列里等待)
      • -3:PROPAGATE(释放资源时需要通知其他节点)
  • ConditionObject
    • ConditionObject和Node一样是AQS的内部类。它用来结合锁实现线程同步,其可以访问AQS的内部变量(state和AQS阻塞队列)。
    • ConditionObject是条件变量,每个条件变量对应一个条件队列,我们可以看到ConditionObject中有两个指针,分别指向条件队列的队尾和队头。条件队列用来存放调用条件变量的await方法后被阻塞的线程。

二、线程中断相关的三个方法

三、Unsafe与LockSupport

Unsafe

  • CAS的全称是Compare-And-Swap,它是一条CPU并发原语。
  • 它的功能是判断内存某个位置是否是预期值,如果是则更改为新的值,这个过程是原子性的
  • CAS并发原语在 java的体现就是sun.mic.Unsafe类个各个方法,调用Unsafe类的方法,JVM会帮助我们实现CAS汇编指令。这是一个完全依赖于硬件的功能,通过它实现原子性操作。由于CAS是一种系统原语,由若干指令组成,该原语执行必须连续的不许中断。

这里设置了静态代码块提前获取了state、head、tail、waitStatus、next四个参数在对象内存中的偏移量。

    private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long stateOffset;private static final long headOffset;private static final long tailOffset;private static final long waitStatusOffset;private static final long nextOffset;static {try {stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("head"));tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));waitStatusOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("waitStatus"));nextOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("next"));} catch (Exception ex) { throw new Error(ex); }}/*** CAS head field. Used only by enq.*/private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);}/*** CAS tail field. Used only by enq.*/private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);}/*** CAS waitStatus field of a node.*/private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);}/*** CAS next field of a node.*/private static final boolean compareAndSetNext(Node node,Node expect,Node update) {return unsafe.compareAndSwapObject(node, nextOffset, expect, update);}

LockSupport

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行

  • park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞: ① 调用unpark函数,释放该线程的许可。② 该线程被中断。③ 设置的时间到了。并且,当time为绝对时间时,isAbsolute为true,否则,isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。
  • unpark函数,释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。
public class LockSupportDemo {public static void main(String[] args) {Thread A = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("线程A被LockSupport.park()阻塞");LockSupport.park();System.out.println("线程A被线程B LockSupport.unpark()唤醒");}},"A");A.start();Thread B = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("线程B唤醒线程A");// 唤醒指定线程t,也就是ALockSupport.unpark(A);}},"B")B.start();}
}结果:
线程ALockSupport.park()阻塞
线程B唤醒线程A
线程A被线程B LockSupport.unpark()唤醒

四、核心源码

以ReentrantLock为例进行讲解,AQS是典型的模板方法的实现,所以AQS对外暴露了多个个抽象方法(tryAcquire、tryRelease等等)需要子类进行实现。
ReentrantLock的lock方法实际上调用了sync的lock方法,而sync继承了AQS,同时针对公平策略和非公平策略有不同的实现。这里我们主要看针对非公平锁NonfairSync的实现。

lock()

    static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}}
    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
        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;}
    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureNode pred = tail;// 获取锁失败,再次判断队列是否初始化if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}
    private Node enq(final Node node) {// 第一次执行,也就是head和tail两个指针都为null,会初始化两个Nodefor (;;) {Node t = tail;// 初始化队列,设置一个空Node,并将head与tail两个指针同时指向该节点if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;// 队列已经初始化完成,则将该节点插入队列尾部} else {node.prev = t;// 注意,此时t仍然指向,为尾节点的上一个节点if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}



    final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 如果当前节点的前置节点是头节点,则意味着本次入队操作是第一次final Node p = node.predecessor();// 如果是第一次入队,则再次尝试获取stateif (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 非第一次入队/第一次入对的第二次循环if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

因为lock方法是不可中断的,所以从lock方法中进来构建起来的同步队列不会有CANCELLED状态。CONDITION用于条件队列当中。PROPAGETE是用于共享模式下的状态。

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {// Acquire失败以后是否需要挂起,true:需要-false:不需要// 针对ReentrantLock,这里指挥判断SIGNALint ws = pred.waitStatus;if (ws == Node.SIGNAL)/** This node has already set status asking a release* to signal it, so it can safely park.*/return true;// ws > 0  =  CANCELLEDif (ws > 0) {/** Predecessor was cancelled. Skip over predecessors and* indicate retry.*/do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {/** waitStatus must be 0 or PROPAGATE.  Indicate that we* need a signal, but don't park yet.  Caller will need to* retry to make sure it cannot acquire before parking.*/compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;}

    private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();}


整体流程:
image.png

unLock()

    public void unlock() {sync.release(1);}
    public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}

image.png

        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;}
    private void unparkSuccessor(Node node) {/** If status is negative (i.e., possibly needing signal) try* to clear in anticipation of signalling.  It is OK if this* fails or if status is changed by waiting thread.*/int ws = node.waitStatus;if (ws < 0)compareAndSetWaitStatus(node, ws, 0);/** Thread to unpark is held in successor, which is normally* just the next node.  But if cancelled or apparently null,* traverse backwards from tail to find the actual* non-cancelled successor.*/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);}

这里线程二被唤醒以后将继续执行acquireQueued方法,判断线程二的前置节点是否为head,如果是则继续使用tryAcquire()方法来尝试获取锁,其实就是使用CAS操作来修改state值,如果修改成功则代表获取锁成功。接着将线程二设置为head节点,然后空置之前的head节点数据,被空置的节点数据等着被垃圾回收

在线程二释放锁以后,这个时候CLH队列中就只剩下线程三:

五、知识拓展

公平锁与非公平锁

非公平锁执行原理:

公平锁执行原理:

参考内容:

  • https://www.bilibili.com/video/BV1vM411r7Bt
  • https://juejin.cn/post/6844904146127044622

相关文章:

AQS源码分析

前言 AbstractQueuedSynchronizer是抽象同步队列&#xff0c;其是实现同步机器的基础组件&#xff0c;并发包中的锁的底层就是使用AQS实现的。AQS中 维护了一个volatile int state&#xff08;代表共享资源&#xff09;和一个FIFO线程等待队列&#xff08;多线程争用资源被阻塞…...

应对Locked勒索病毒威胁:你的数据安全准备好了吗?

导言&#xff1a; .Locked勒索病毒&#xff0c;作为一种新型的恶意软件&#xff0c;已经在全球范围内引起了广泛的关注。这种病毒通过加密受害者的文件&#xff0c;并要求支付赎金以获取解密密钥&#xff0c;从而实现对受害者的勒索。本文旨在深入解析.Locked勒索病毒的特点、…...

周末分享一篇关于html和http的文章吧

前面咱们说了https://blog.csdn.net/luohaitao/article/details/136974344&#xff08;说道说道JSP和HTTP吧-CSDN博客&#xff09;&#xff0c;把http的方法和jsp中httpservle对象的方法对上号了&#xff0c;其实从开发的角度看&#xff0c;jsp就是html中混入了java的服务端代码…...

Frechet分布

Frechet分布是一种连续概率分布&#xff0c;它是极值统计中的一个重要模型&#xff0c;尤其在分析极端事件&#xff08;如洪水、地震、金融市场中的极端波动&#xff09;的最大值极限分布时扮演关键角色。Frechet分布属于极值分布的三种基本类型&#xff08;I型、II型、III型&a…...

vue3全局引入element-plus使用Message教程

文章目录 安装引入 Element Plus和组件样式示例注意安装与引入&#xff1a;按需引入&#xff1a;API 使用&#xff1a;样式问题&#xff1a;组件上下文&#xff1a;版本兼容性&#xff1a;错误处理&#xff1a; 这是 Element UI 的 Vue 3 版本。ElMessage 是 Element Plus 中的…...

时序预测 | Matlab实现BiTCN-BiLSTM双向时间卷积神经网络结合双向长短期记忆神经网络时间序列预测

时序预测 | Matlab实现BiTCN-BiLSTM双向时间卷积神经网络结合双向长短期记忆神经网络时间序列预测 目录 时序预测 | Matlab实现BiTCN-BiLSTM双向时间卷积神经网络结合双向长短期记忆神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现BiTCN…...

基于 Linux 的更新版 MaxPatrol VM 可扫描 Windows

&#x1f47e; MaxPatrol VM 2.1 是俄罗斯唯一一款可以安装在 Linux 上并以审计和五重测试模式扫描 Windows 主机&#xff08;甚至是旧版本&#xff09;的漏洞管理产品。 让我们告诉你更新后的 MaxPatrol VM 还有哪些有用的功能&#xff1a; 1. 由于采用了新的数据存储模式&a…...

【软件开发】给Ubuntu 18.04虚拟机安装最新的Python 3.12.2

一、前言 笔者在Windows 11主机上安装有Ubuntu 18.04虚拟机&#xff08;使用虚拟化平台Oracle VM VirtualBox&#xff09;&#xff0c;在Python3.6的使用过程中遇到了问题&#xff0c;决定安装Python 3.12.2&#xff0c;在此记录安装过程。 二、安装过程&#xff08;在Ubuntu…...

鸿蒙NXET实战:高德地图定位SDK【获取Key+获取定位数据】(二)

如何申请key 1、创建新应用 进入[控制台]&#xff0c;创建一个新应用。如果您之前已经创建过应用&#xff0c;可直接跳过这个步骤。 2、添加新Key 在创建的应用上点击"添加新Key"按钮&#xff0c;在弹出的对话框中&#xff0c;依次&#xff1a;输入应用名名称&…...

Dubbo管理控制台

1.将资料中的dubbo-admin-2.6.0.war文件复制到tomcat的webapps目录下 2.启动tomcat,修改WEB-INF下的dubbo.properties文件 #如果Zookeeper是安装在虚拟机上的那么注册中心的地址需要修改为虚拟机的ip地址 dubbo.registry.addresszookeeper://192.168.100.110:2181 dubbo.admin…...

CSS问题精粹1

1.关于消除<li>列表前的符号 我相信很多人在初学CSS时会遇到该问题&#xff0c;无论是创作导航&#xff0c;还是列表&#xff0c;前面都会有个黑点点或其它符号。 解决该问题其实很简单 采用list-style-type:none或list-style:none直接解决 如果你想更换前面的黑点点&a…...

neo4j所有关系只显示RELATION,而不显示具体的关系

当看r时&#xff0c;真正的关系在properties中的type里&#xff0c;而type为“RELATION” 造成这个的原因是&#xff1a; 在创建关系时&#xff0c;需要指定关系的类型&#xff0c;这是固定的&#xff0c;不能像属性那样从CSV文件的一个字段动态赋值。标准的Cypher查询语言不支…...

VMware和Xshell连接

1.开启虚拟机 2.使用管理员账户&#xff0c;点击未列出 3.输入用户名密码 4.点击编辑虚拟网络编辑器 5.记住自己的网关和IP地址 6.打开终端 7.输入命令&#xff0c;vim / etc / sysconfig / network -scripts / ifcfg-ens33 回车 8.修改图中两处按“ I ”键进入编辑 d…...

【C语言进阶篇】编译和链接

【C语言进阶篇】编译和链接 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;C语言&#x1f353; &#x1f33c;文章目录&#x1f33c; 编译环境与运行环境 1. 翻译环境 2. 编译环境&#xff1a;预编译&#xff08;预处理&#xff09;编…...

pytorch+tensorboard

安装依赖 pip install teorboard pip install torch_tb_profiler了解teorboard 记录并可视化标量[组]、图片[组]。 如何使用 第一步:构建模型,记录中间值,写入summarywriter 每次写入一个标量add_scalar 比如: from torch.utils.tensorboard import SummaryWriter wr…...

PTA------ 敲笨钟

字符串处理问题&#xff01;------->字符串处理相关操做 代码&#xff1a; #include <iostream> #include<algorithm> #include<cmath> #include<cstring> #include<set> #include<stack> #include<queue> #include<map>…...

关于HashSet的五个问题

1.HashSet集合的底层数据结构是什么样的? HashSet 集合的底层数据结构是哈希表&#xff0c;它是由一个数组和链表&#xff08;或红黑树&#xff0c;具体取决于 JDK 版本&#xff09;组成的数据结构。 数组&#xff1a;哈希表的主要部分是一个数组&#xff0c;它的每个位置称为…...

linux性能调优汇总(一)cpu

目录 一、引言 二、CPU ------>2.1、/proc/cpuinfo ------>2.2、cpuid指令 ------>2.3、lscpu ------>2.4、turbostat ------>2.5、rdmsr ------>2.6、perf ------>2.7、top ------>2.8、ps ------>2.9、pidstat 查看每个进程CPU、内存、…...

CSS object-fit 属性

object-fit 属性指定元素的内容应该如何去适应指定容器的高度与宽度。 object-fit 一般用于 img 和 video 标签&#xff0c;一般可以对这些元素进行保留原始比例的剪切、缩放或者直接进行拉伸等。 您可以通过使用 object-position 属性来切换被替换元素的内容对象在元素框内的…...

使用LangChain LCEL生成RAG应用、使用LangChain TruLens对抗RAG幻觉

# 导入LangChain的库 from langchain import *# 加载数据源 loader WebBaseLoader() doc loader.load("https://xxx.html")# 分割文档对象 splitter RecursiveCharacterTextSplitter(max_length512) docs splitter.split(doc)# 转换文档对象为嵌入&#xff0c;并…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

面试高频问题

文章目录 &#x1f680; 消息队列核心技术揭秘&#xff1a;从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"&#xff1f;性能背后的秘密1.1 顺序写入与零拷贝&#xff1a;性能的双引擎1.2 分区并行&#xff1a;数据的"八车道高速公路"1.3 页缓存与批量处理…...

【版本控制】GitHub Desktop 入门教程与开源协作全流程解析

目录 0 引言1 GitHub Desktop 入门教程1.1 安装与基础配置1.2 核心功能使用指南仓库管理日常开发流程分支管理 2 GitHub 开源协作流程详解2.1 Fork & Pull Request 模型2.2 完整协作流程步骤步骤 1: Fork&#xff08;创建个人副本&#xff09;步骤 2: Clone&#xff08;克隆…...

基于Java项目的Karate API测试

Karate 实现了可以只编写Feature 文件进行测试,但是对于熟悉Java语言的开发或是测试人员,可以通过编程方式集成 Karate 丰富的自动化和数据断言功能。 本篇快速介绍在Java Maven项目中编写和运行测试的示例。 创建Maven项目 最简单的创建项目的方式就是创建一个目录,里面…...

软件工程教学评价

王海林老师您好。 您的《软件工程》课程成功地将宏观的理论与具体的实践相结合。上半学期的理论教学中&#xff0c;您通过丰富的实例&#xff0c;将“高内聚低耦合”、SOLID原则等抽象概念解释得十分透彻&#xff0c;让这些理论不再是停留在纸面的名词&#xff0c;而是可以指导…...

【学习记录】使用 Kali Linux 与 Hashcat 进行 WiFi 安全分析:合法的安全测试指南

文章目录 &#x1f4cc; 前言&#x1f9f0; 一、前期准备✅ 安装 Kali Linux✅ 获取支持监听模式的无线网卡 &#x1f6e0; 二、使用 Kali Linux 进行 WiFi 安全测试步骤 1&#xff1a;插入无线网卡并确认识别步骤 2&#xff1a;开启监听模式步骤 3&#xff1a;扫描附近的 WiFi…...

大模型智能体核心技术:CoT与ReAct深度解析

**导读&#xff1a;**在当今AI技术快速发展的背景下&#xff0c;大模型的推理能力和可解释性成为业界关注的焦点。本文深入解析了两项核心技术&#xff1a;CoT&#xff08;思维链&#xff09;和ReAct&#xff08;推理与行动&#xff09;&#xff0c;这两种方法正在重新定义大模…...

【向量库】Weaviate 搜索与索引技术:从基础概念到性能优化

文章目录 零、概述一、搜索技术分类1. 向量搜索&#xff1a;捕捉语义的智能检索2. 关键字搜索&#xff1a;精确匹配的传统方案3. 混合搜索&#xff1a;语义与精确的双重保障 二、向量检索技术分类1. HNSW索引&#xff1a;大规模数据的高效引擎2. Flat索引&#xff1a;小规模数据…...