【工作记录】AQS学习笔记

简介

在Java中,AbstractQueuedSynchronizer(AQS)是Java并发包(java.util.concurrent.locks)中一个用于构建锁和同步器框架的基础类。提供了一种实现阻塞锁和其他同步组件的底层机制。

基本原理概述

它的核心原理包括以下关键点:

  • 状态管理:
    AQS通过一个volatile类型的整型变量state来表示同步状态。比如在独占锁(如ReentrantLock)中,state为0表示锁未被任何线程持有,大于0则表示当前持有锁的线程数量以及重入次数。
  • 等待队列:
    AQS维护了一个FIFO双向链表作为同步队列,即CLH队列,用于存放等待获取锁或同步状态的线程。当线程尝试获取锁但发现状态不可用时,会将自己包装成一个节点(Node)并加入到队列尾部进行自旋或挂起等待。
  • 获取/释放同步状态:
    提供了tryAcquire()和tryRelease()等模板方法给子类去具体实现。这些方法决定了如何基于state值去尝试获取或释放同步状态。例如,在非公平锁中,tryAcquire()可能直接尝试获取锁,而在公平锁中,它会检查是否有其他线程等待更长时间。
  • 线程阻塞与唤醒:
    利用Unsafe类或者其他并发工具对线程进行阻塞和唤醒操作。当线程无法立即获取锁时,会调用acquireQueued()方法将线程放入等待队列,并进入park()方法挂起;而当锁释放时,则会从等待队列中的某个节点开始唤醒等待线程,使其重新尝试获取锁。
  • 可重入性支持:
    AQS可以支持重入,这意味着已经获得锁的线程可以再次成功请求该锁,对应的state会递增以记录重入次数。
  • 共享模式与独占模式:
    AQS同时支持独占模式(只有一个线程能获取到同步状态)和共享模式(多个线程可以同时获取到同步状态),分别对应于ReentrantLock、Semaphore等不同的并发组件。
  • 中断处理:
    当等待线程被中断时,AQS会根据中断策略进行相应处理,这通常由具体的同步组件决定,可以通过覆盖tryAcquireSharedInterruptibly()等方法实现。
    通过继承AQS并实现上述抽象方法,开发者可以创建各种复杂的同步组件,如互斥锁、信号量、读写锁等,无需关注底层的线程调度和阻塞/唤醒逻辑,大大简化了并发编程的复杂度。

核心流程说明

AbstractQueuedSynchronizer(AQS)的流程主要包括线程获取和释放同步状态以及在无法立即获取时如何进入等待队列、唤醒后续线程等步骤。

以下是其核心流程概述:

  1. 获取同步状态:

    • 当线程尝试获取同步状态时,首先调用子类重写的tryAcquire(int arg)方法。
      • 如果该方法返回true,表示线程成功获取到同步状态,并执行相应的业务逻辑。
      • 如果返回false,表示当前不能获取到同步状态,线程需要被放入同步队列中等待。
     public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
    
    • addWaiter(Node.EXCLUSIVE)将当前线程包装成一个独占模式的Node节点并插入同步队列尾部。
    • acquireQueued(Node node, int arg)让线程在队列中自旋或阻塞等待,直到获取到同步状态或者被中断。
  2. 等待与自旋:

    • 在同步队列中的线程会不断地检查自己的前驱节点是否为头节点,如果是,则再次尝试获取同步状态。
    • 若非头节点或尝试获取失败,则通过循环+CAS的方式更新节点的waitStatus值,并可能进入Park操作进行线程挂起。
  3. 释放同步状态:

    • 线程在完成任务后调用release(int arg)来释放同步状态。
    • 调用子类重写的tryRelease(int releases)方法,如果该方法返回true,表示同步状态成功释放
    public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;
    }
    
  4. 唤醒等待线程:

    • unparkSuccessor(Node node)方法从同步队列中找到第一个处于等待状态且合法的节点(通常是头节点的下一个节点),然后调用LockSupport.unpark(s.thread)解除该节点关联线程的阻塞状态,使其有机会再次尝试获取同步状态。
  5. 可重入性支持:
    AQS通过维护一个计数器state来支持锁的可重入性。每当持有锁的线程再次请求时,state递增;当线程退出同步代码块时,state递减,直到state为0时其他线程才能获取到锁。

整个AQS的工作流程围绕着对state变量的操作以及同步队列的管理展开,有效地实现了锁的获取和释放以及线程间的同步协作。

AQS关键源码解析

以下是结合上述核心流程涉及到的关键源码解析:

  1. 状态管理(state)

    private volatile int state; // 核心状态变量,volatile保证可见性和有序性protected final boolean compareAndSetState(int expect, int update) {return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    

    state字段表示同步状态,通过CAS操作(compareAndSetState方法)来保证原子性的更新。

  2. 同步队列(CLH队列)

    static final class Node {volatile Node prev;volatile Node next;volatile Thread thread;}transient volatile Node head;
    transient volatile Node tail;   
    

​ AQS内部维护了一个FIFO双向链表作为同步队列,节点类型为Node,每个节点代表一个等待获取同步状态的线程。其中head指向队列头节点,tail指向队列尾节点。

  1. 获取同步状态(acquire)

    public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
    }protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();
    }// 子类需要重写tryAcquire方法以实现自定义同步策略
    
    • acquire()方法尝试获取同步状态,首先调用子类实现的tryAcquire()方法尝试获取,若失败,则将当前线程包装成Node并加入到同步队列中,然后在队列中进行自旋或阻塞等待。
    • addWaiter()方法将线程封装为Node并插入队列尾部。
    • acquireQueued()方法让线程在队列中进行循环等待,直到获取到同步状态或被中断。
  2. 释放同步状态(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;
    }protected boolean tryRelease(int releases) {throw new UnsupportedOperationException();
    }// 子类需要重写tryRelease方法以实现同步状态的释放逻辑
    

    release()方法尝试释放同步状态,首先调用子类实现的tryRelease()方法释放资源,成功后检查头结点状态,并唤醒其后继节点上的线程。

  3. 唤醒等待线程(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);
    }
    

    当同步状态释放时,会调用unparkSuccessor()方法从同步队列中找到第一个等待状态合法的节点,并解除该节点所关联线程的阻塞状态。

总之,AQS通过上述机制提供了一种基础架构,使得开发者可以基于此实现各种复杂的同步组件,如ReentrantLock、Semaphore、CountDownLatch等。

与Synchronized对比

对比项synchronizedAQS
功能性1. 内置关键字,无需额外引入类库。
2. 支持互斥性和可见性,确保同一时间只有一个线程可以访问同步代码块或方法。
3. 自动管理锁的获取和释放,支持可重入,即一个线程获取到锁后还能再次进入加锁区域。
4. 不提供超时等待锁的功能,且不支持中断请求。
1. 是一个底层框架,用于构建更高级别的并发工具如ReentrantLock、Semaphore、CountDownLatch等。
2. 同样支持互斥性和可见性,并通过state变量实现了可重入。
3. 提供了比synchronized更多的功能选项
灵活性使用简单,语法直观,但控制粒度相对较粗,只能以整个对象或者方法为单位进行加锁。更灵活,可以通过自定义同步器实现更多定制化的同步需求,比如复杂的条件等待、读写锁等功能。
性能在JDK1.6及以后版本中进行了很多优化,如适应性自旋、锁消除、锁粗化等,性能已经相当高,在许多常见场景下与基于AQS的锁性能相近。在某些特定场景下可能有更好的性能表现,如使用自旋锁避免上下文切换,以及通过“工作窃取”算法减少线程间的竞争。
但是,如果使用不当(如过于频繁地创建和销毁AQS实例),可能会导致性能下降。

总结

本文对AQS的基本原理和关键代码做了简单解析,同时对比了aqs和synchronized的区别。

创作不易,欢迎一键三连~~

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

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

相关文章

C++知识点总结(17):贪心算法

贪心算法 一、文件操作1. 加头文件2. 加函数3. 写输入 二、贪心算法1. 概念2. 洛谷P2240 最多的金币2.1 审题2.2 解题思路2.3 参考答案 3. 洛谷P2240反向3.1 审题3.2 参考答案 4. 贪心的资本家4.1 审题4.2 参考答案 一、文件操作 1. 加头文件 #include <cstdio>cstdio …

牛刀小试 - C++ 学生信息管理系统

参考文档&#xff1a; C实现&#xff1a;学生管理系统&#xff08;详细解析&#xff09; 关于switch中的default里面的break是否可以省略这件事 需求要求&#xff1a; &#xff08;1&#xff09;增加记录 &#xff08;2&#xff09;查找记录 &#xff08;3&#xff09;删除…

FFmpeg进阶-给视频添加马赛克效果

很多时候为了隐藏视频中的敏感信息如人脸、身份证号、车牌号等,我们会采用马赛克算法对视频帧中的一部分内容进行处理。这里介绍一下如何采用FFmpeg实现马赛克效果。 马赛克效果算法的原理如下: 1.分块处理:首先将图像划分为多个小块或区域 2.像素替换:对于每个小块,算法会将…

【Windows11】内存使用率只有总内存容量的一半

硬件配置&#xff1a;2根16G的内存条&#xff08;金百达DDR4 3600, 海力士颗粒&#xff0c;灯条刃&#xff09;&#xff0c;插在2和4通道上 系统配置&#xff1a;windows11 问题描述&#xff0c;我和这位网友的问题一模一样&#xff1a; 系统32G内存&#xff0c;开机完全识别&…

实习日志17

1.测试公司服务器 1.1.太卡了&#xff0c;点一下卡半天 上传项目源码和文件&#xff1a; 将活字格项目源码和所有相关文件上传到服务器的指定目录。可以使用向日葵远程控制或者版本控制系统来进行文件上传。 重新配置活字格项目&#xff1a; 根据服务器环境&#xff0c;需要对…

Java 学习和实践笔记(14):用表格来理解类、对象、属性以及动作(方法)很容易

OOP :面向对象编程&#xff0c;object oriented programming. 用表格就可以很好地理解类、对象、属性、以及动作这些概念。 一个表&#xff08;结构&#xff09;就对应一个类&#xff08;结构&#xff09;。所以凡叫什么类&#xff0c;自己就在心里把它叫什么表。反过来&…

设计模式-创建型模式-简单工厂模式

0 引言 简单工厂模式&#xff08;Simple Factory Pattern&#xff09;&#xff1a;定义一个工厂类&#xff0c;它可以根据参数的不同返回不同类的实例&#xff0c;被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态&#xff08;static&#xf…

vue 非父子通信-event bus 事件总线

1.作用 非父子组件之间&#xff0c;进行简易消息传递。(复杂场景→ Vuex) 2.步骤 创建一个都能访问的事件总线 &#xff08;空Vue实例&#xff09; import Vue from vue const Bus new Vue() export default Bus A组件&#xff08;接受方&#xff09;&#xff0c;监听Bus的…

【图与网络数学模型】3.Ford-Fulkerson算法求解网络最大流问题

【图与网络数学模型】3.Ford-Fulkerson算法求解网络最大流问题 一、网络流模型1. 概述2. 可行流3. 增广链 二、示例1. 最大流问题2. Alternate Formulation&#xff1a;最小截量问题 三、Ford-Fulkerson 算法1. 导入库2. 初始化残差图3. 定义查找增广路径4. 定义循环5. 程序运行…

ubuntu22.04@Jetson Orin Nano之OpenCV安装

ubuntu22.04Jetson Orin Nano之OpenCV安装 1. 源由2. 分析3. 证实3.1 jtop安装3.2 jtop指令3.3 GPU支持情况 4. 安装OpenCV4.1 修改内容4.2 Python2环境【不需要】4.3 ubuntu22.04环境4.4 国内/本地环境问题4.5 cudnn版本问题 5. 总结6. 参考资料 1. 源由 昨天用Jetson跑demo程…

揭秘 LLMs 时代向量数据库的 3 大实用场景

过去一年&#xff0c;ChatGPT 和其他大语言模型&#xff08;LLMs&#xff09;的爆火也带动了向量数据库的发展。 许多用户在搭建检索增强生成&#xff08;RAG&#xff09;系统过程中选择了使用向量数据库 Zilliz Cloud &#xff0c;但 Zilliz Cloud 的功能不止于此&#xff0c;…

Git如何使用 五分钟快速入门

Git如何使用 五分钟快速入门 Git是一个分布式版本控制系统&#xff0c;它可以帮助开发人员跟踪和管理项目的代码变更。与传统的集中式版本控制系统&#xff08;如SVN&#xff09;不同&#xff0c;Git允许开发人员在本地存储完整的代码仓库&#xff0c;并且可以独立地进行代码修…

GEE数据更新——MODIS数据LST地表温度计算案例MODIS/006/ MOD11A1 已弃用并且没有新数据。请改用MODIS/061/ MOD11A1

问题 为什么我无法在下面给出的代码中下载 2024 年的数据?看起来有效期到 2022 年 11 月。你能帮我吗? 差异 MODIS/006/ MOD11A1和MODIS/061/ MOD11A1是两个不同的MODIS地表温度数据集。它们之间的差异主要体现在数据处理方法和时间范围上。目前GEE中已经没有前者的数据。…

《英伟达-本地AI》--NVIDIA Chat with RTX-本机部署

阿丹&#xff1a; 突然发现公司给配置的电脑是NVIDIA RTX 4060的显卡&#xff0c;这不搞一搞本地部署的大模型玩一玩&#xff1f;&#xff1f;&#xff1f; 从0-》1记录一下本地部署的全过程。 本地模型下载地址&#xff1a; Build a Custom LLM with Chat With RTX | NVIDIA…

虚拟机安装Centos7迷你版

虚拟机安装Centos7迷你版 迷你版较常规版精简了很多功能&#xff0c;所以体积也小了很多&#xff0c;喜欢自定义安装的可以选择迷你版 参考文章&#xff1a; VirtualBox虚拟机安装Centos7详细教程图解 网卡配置参考文章 下载Centos迷你版镜像 安装镜像 下载镜像 阿里云Cen…

Python 安装和使用的IDE

安装 安装 Anaconda (包较大), 包含了一些库安装 Miniconda (包较小) 只和 python 打包, 没有其他的包, 用的时候需要自己安装 conda install pandaspython pythonipython 有更多的功能 ipythonjupyter jupyter notebookjupyterlab 可以去 github 查看 conda install -c…

理论学习-ARM-内核

ARM内核 函数的调用加载、存储计算中断异常线程的切换 为了提高学习效率&#xff0c;我们要提前想好学习策略。 首先&#xff0c;使用频率越高的知识点&#xff0c;越要首先学习。假使&#xff0c;我们学习了一个知识点&#xff0c;能覆盖工作中80%的工作量&#xff0c;那是不是…

机器学习基础(四)非监督学习的进阶探索

导语&#xff1a;上一节我们详细探索监督学习的进阶应用&#xff0c;详情可见&#xff1a; 机器学习基础&#xff08;三&#xff09;监督学习的进阶探索-CSDN博客文章浏览阅读296次&#xff0c;点赞13次&#xff0c;收藏11次。监督学习作为机器学习的一个主要分支&#xff0c;…

【C++】vector模拟实现+迭代器失效

vector模拟实现 成员变量定义默认成员函数构造函数 迭代器范围for、对象类型匹配原则 容量操作sizeemptycapacityreserve成员变量未更新memcpy值拷贝 resize内置类型的构造函数 数据访问frontbackoperator[ ] 数据修改操作push_backpop_backswapclearinsertpos位置未更新无返回…

解决缓存失效导致的数据库压力问题(缓存击穿问题)

问题描述 在软件开发过程中&#xff0c;特别是在使用缓存策略优化数据访问性能时&#xff0c;经常会遇到缓存失效引发的问题。具体来说&#xff0c;在一个服务类BaseDataService中&#xff0c;findData方法负责从数据库拉取数据并缓存。这里使用了expireAfterWrite60s的缓存策…