队列同步器AQS原理分析及具体实现

2023-11-13

Java中的并发编程很多都是以队列同步器AbstractQueuedSynchronizer为基础的, 例如ReentrantLock,CountDownLatch等。

下面介绍其构成以及相应的实现。

构成

private volatile int state;

AbstractQueuedSynchronizer中通过一个int类型的成员变量state来表示同步状态,该变量使用volatile来修饰,保证多线程之间的可见性。

以及一个内置的队列来完成资源排队。

其中对于state有如下几个操作方法:

  • getState() 获取同步状态
  • setState() 设置同步状态
  • compareAndSetState() 使用CAS设置同步状态,该方法能保证状态设置的原子性
具体原理

同步器依赖内部的一个双向队列来完成同步状态的管理。该队列遵循先入先出的原则。

当线程获取同步状态失败时,同步器会执行以下步骤:

  1. 构造一个节点Node, 该节点包含当前线程、当前线程的等待状态、前驱节点引用、后驱节点引用等信息
  2. 将该节点加入到队列当中(加入到队列的尾结点)
  3. 阻塞该线程

当释放同步状态时,只会将首节点的线程唤醒, 使其尝试获取同步状态。

image-20210407214734999

同步器中包含了指向头结点和尾结点的应用head, tail, 这两个节点作用分别如下:

  • 首节点是获取同步状态成功的节点, 首节点的线程在释放同步状态时, 会唤醒后继节点, 当后继节点成功获取到同步状态时,就会将自己设置为首节点, 以此类推。
  • 因为新加入的节点都会放在尾节点, 所以为了线程安全,提供了一个利用CAS设置尾结点的方法compareAndSetTail()。
同步状态获取

同步状态获取的方式分为两种:

  • 独占式获取同步状态, 在同一时刻只能有一个线程能获取到同步状态,其他线程都将会被阻塞,例如文件的写操作。 对应acquire()方法
  • 共享式获取同步状态, 在同一时刻允许有多个线程获取到同步状态,例如文件的读操作。对应acquireShared()方法

下图是同步器独占式获取同步状态的流程图。

image-20210407220327281

ReentrantLock

可重入锁。支持一个线程对资源的重复加锁。

同时, 该锁还支持是否公平的获取锁。注意,synchronized支持的锁也是可重入的,因为它会在对象头存储获取锁的线程ID。

几个问题:

  1. ReentrantLock是如何实现可重入的呢?

首先ReentrantLock组合了同步器AbstractQueuedSynchronizer来实现锁的功能。其中state在ReentrantLock用来表示线程获取锁的次数,当线程获取锁时:

​ 需要判断当前线程是否是获取锁的线程, 如果是则将state值加1, 并返回获取锁成功的标识, 这样就能保证可重入。

  1. 锁是如何释放的?

答案是仍然使用state来进行释放。 当调用unlock()释放锁时, 同步器会将state的值减1, 只有当state的值为0时, 才能返回锁释放成功的标识。

  1. 公平锁时如何实现的?

ReentrantLock默认是非公平锁, 在获取锁时,只要使用CAS设置同步状态成功, 那么就表示该线程就获取锁成功。

对于公平锁,在获取同步状态时, 还需要判断同步队列中当前节点是否有前驱节点, 如果有, 则说明有更早的线程在等待获取锁, 则当前线程需要加入到等待队列的尾结点, 等待之前所有线程获取节点成功并释放之后才能尝试获取锁。

ReentrantReadWriteLock

可重入的读写锁。维护了一个读锁和一个写锁。其中读锁是共享锁, 同一时刻可以有多个线程拥有,写锁是排他锁, 同一时刻只能有一个写锁。

其也是组合AbstractQueuedSynchronizer来实现的。具体如何实现的呢?

关键也是state变量的设计, 将state变量按位拆成2部分, 高16位表示读状态, 低16位表示写状态, 在进行读锁和写锁的操作时,和ReentrantLock类似,对这两个状态进行加减运算。

写锁获取流程:

  1. 如果当前线程已经获取了写锁, 则增加写状态,并返回获取写锁成功的标识。此处保证了可重入
  2. 如果有其他线程获取了读锁或写锁,则当前线程进入等待状态,等待其他线程的写锁和读锁全部释放完毕, 再去尝试获取。此处保证了排他性。

读锁获取流程:

  1. 如果有其他线程获取了写锁, 则当前线程进入等待状态。
  2. 如果没有其他线程获取了写锁,读锁总是能保证成功获取。特别的,如果当前线程已经获取了读锁, 则增加读状态(保证可重入性),并返回获取读锁成功的标识。
CountDownLatch

某个任务的执行,需要等到多个线程都执行完毕之后才可以进行。 该场景可以使用CountDownLatch来实现。

其也是组合AbstractQueuedSynchronizer来实现的。关键是state的设计。state被用来表示需要等待的线程个数

在CountDownLatch中,主要涉及到如下三个方法:

  • 构造函数中count参数, 指定需要等待的线程个数, 也就是state变量
  • countDown()方法, 每调用一次,state减1
  • await()方法, 等待, 只有当state等于0时, 才会从该方法处返回, 否则会一直阻塞。

注意, countDown()方法最好写在finally中, 防止发生死锁。因为如果在调用countDown()之前程序发生了异常, 导致该方法没有执行,那么state变量就永远不可能为0, 此时调用await()方法的线程则会永远阻塞在该处。

或者使用await(long timeout, TimeUnit unit), 加入超时参数, 当达到超时时间自动返回。

参考资料:

为0, 此时调用await()方法的线程则会永远阻塞在该处。

或者使用await(long timeout, TimeUnit unit), 加入超时参数, 当达到超时时间自动返回。

参考资料:

  1. 《Java并发编程的艺术》
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

队列同步器AQS原理分析及具体实现 的相关文章

  • 带路径压缩算法的加权 Quick-Union

    有一种 带路径压缩的加权快速联合 算法 代码 public class WeightedQU private int id private int iz public WeightedQU int N id new int N iz new
  • java中监视目录变化

    我正在使用 WatchService 来监视目录中的更改 特别是目录中新文件的创建 下面是我的代码 package watcher import java nio file import static java nio file Stand
  • 垃圾收集器如何在幕后工作来收集死对象?

    我正在阅读有关垃圾收集的内容 众所周知 垃圾收集会收集死亡对象并回收内存 我的问题是 Collector 如何知道任何对象已死亡 它使用什么数据结构来跟踪活动对象 我正在研究这个问题 我发现GC实际上会跟踪活动对象 并标记它们 每个未标记的
  • eclipse行号状态行贡献项是如何实现的?

    我需要更新状态行编辑器特定的信息 我已经有了自己的实现 但我想看看 eclipse 贡献项是如何实现的 它显示状态行中的行号 列位置 谁能指点一下 哪里可以找到源代码 提前致谢 亚历克斯 G 我一直在研究它 它非常复杂 我不确定我是否了解完
  • Java 的支持向量机?

    我想用Java编写一个 智能监视器 它可以随时发出警报detects即将到来的性能问题 我的 Java 应用程序正在以结构化格式将数据写入日志文件
  • 为什么即使我的哈希码值相同,“==”也会返回 false

    我写了一个像这样的课程 public class HashCodeImpl public int hashCode return 1 public static void main String args TODO Auto generat
  • Android中如何使用JNI获取设备ID?

    我想从 c 获取 IMEIJNI 我使用下面的代码 但是遇到了未能获取的错误cls 它总是返回NULL 我检查了环境和上下文 它们都没有问题 为什么我不能得到Context班级 我在网上搜索了一下 有人说我们应该使用java lang Ob
  • 从 MATLAB 调用 Java?

    我想要Matlab程序调用java文件 最好有一个例子 需要考虑三种情况 Java 内置库 也就是说 任何描述的here http docs oracle com javase 6 docs api 这些项目可以直接调用 例如 map ja
  • 将巨大的模式编译成Java

    有两个主要工具提供了将 XSD 模式编译为 Java 的方法 xmlbeans 和 JAXB 问题是 XSD 模式确实很大 30MB 的 XML 文件 大部分模式在我的项目中没有使用 所以我可以注释掉大部分代码 但这不是一个好的解决方案 目
  • Mockito 使用 @Mock 时将 Null 值注入到 Spring bean 中?

    由于我是 Spring Test MVC 的新手 我不明白这个问题 我从以下代码中获取了http markchensblog blogspot in search label Spring http markchensblog blogsp
  • 断言 Kafka 发送有效

    我正在使用 Spring Boot 编写一个应用程序 因此要写信给 Kafka 我这样做 Autowired private KafkaTemplate
  • Java 中如何将 char 转换为 int? [复制]

    这个问题在这里已经有答案了 我是Java编程新手 我有例如 char x 9 我需要得到撇号中的数字 即数字 9 本身 我尝试执行以下操作 char x 9 int y int x 但没有成功 那么我应该怎么做才能得到撇号中的数字呢 ASC
  • Jetty、websocket、java.lang.RuntimeException:无法加载平台配置器

    我尝试在 Endpoint 中获取 http 会话 我遵循了这个建议https stackoverflow com a 17994303 https stackoverflow com a 17994303 这就是我这样做的原因 publi
  • JDBC 时间戳和日期 GMT 问题

    我有一个 JDBC 日期列 如果我使用 getDate 则会得到 date 仅部分2009 年 10 月 2 日但如果我使用 getTimestamp 我会得到完整的 date 2009 年 10 月 2 日 13 56 78 890 这正
  • 不可变的最终变量应该始终是静态的吗? [复制]

    这个问题在这里已经有答案了 在java中 如果一个变量是不可变的并且是final的 那么它应该是一个静态类变量吗 我问这个问题是因为每次类的实例使用它时创建一个新对象似乎很浪费 因为无论如何它总是相同的 Example 每次调用方法时都会创
  • 为什么\0在java中不同系统中打印不同的输出

    下面的代码在不同的系统中打印不同的输出 String s hello vsrd replace 0 System out println s 当我在我的系统中尝试时 Linux Ubuntu Netbeans 7 1 它打印 When I
  • Hibernate 本机查询 - char(3) 列

    我在 Oracle 中有一个表 其中列 SC CUR CODE 是 CHAR 3 当我做 Query q2 em createNativeQuery select sc cur code sc amount from sector cost
  • Java/Python 中的快速 IPC/Socket 通信

    我的应用程序中需要两个进程 Java 和 Python 进行通信 我注意到套接字通信占用了 93 的运行时间 为什么通讯这么慢 我应该寻找套接字通信的替代方案还是可以使其更快 更新 我发现了一个简单的修复方法 由于某些未知原因 缓冲输出流似
  • Java RMI - 客户端超时

    我正在使用 Java RMI 构建分布式系统 它必须支持服务器丢失 如果我的客户端使用 RMI 连接到服务器 如果该服务器出现故障 例如电缆问题 我的客户端应该会收到异常 以便它可以连接到其他服务器 但是当服务器出现故障时 我的客户端什么也
  • Java 11 - 将 Spring @PostConstruct 替换为 afterPropertiesSet 或使用 initMethod

    我正在使用 spring 应用程序 有时会使用 PostConstruct用于代码和测试中的设置 看来注释将被排除在外Java 11 https www baeldung com spring postconstruct predestro

随机推荐

  • [一步一步学react系列] 04—计算器Demo

    前言 之前的例子都是写的计数器 加一减一的功能 我们大致弄懂了redux分层和store数据管理 下面我们将结合现有知识写一个终极版的计算器 以此巩固所学知识 知识点 redux分层 react router 一些算法及数据结构知识 栈 中
  • 调用ChatGpt openai官方node.js包Error: connect ETIMEDOUT问题

    原因是调用的axios库不走系统代理 需要额外配置 openai在文档中有说明增加axios配置的方法 只需请求时配置下proxy就ok了
  • Understand(代码分析工具)的安装教程

    前言 最近在学习嵌入式系统时 写的代码越来越多 一个文件里面函数的数量也越来越多 为方便查看写了哪些函数 以及文件总体架构 在网上找了半天 找到了Understand这款神器 相比于vscode 该软件占内较少 查看结构更直接 文章目录 前
  • JavaScript 获取数组的最后一个元素

    index取值 args args length 1 pop方法 args pop 注意 pop方法会删除args最后一个元素 并返回
  • RS485、MODBUS通信协议浅显易懂篇

    前言 MODBUS协议是Modicon公司发表的一种串行通信协议 属于OSI模型中应用层的协议 现广泛应用于工业控制领域 它的主要特点是免费开放 支持多种电气接口 如RS 232 RS 485 传输介质可以是双绞线 光纤 无线等 RS485
  • 计算机网络基础概论

    什么是Internet 从具体构成角度看 端系统 主机节点 主机及其上运行的网络应用程序 和网络交换设备 数据交换节点 中继器 路由器 交换机 负载均衡设备等 边 通信链路 分为接入网链路和骨干链路 接入网链路是指主机连接到互联网的链路 骨
  • java开发异常类型汇总

    1 java lang nullpointerexception 这个异常大家肯定都经常遇到 异常的解释是 程序遇上了空指针 简单地说就是调用了未经初始化的对象或者是不存在的对象 这个错误经常出现在创建图片 调用数组这些操作中 比如图片未经
  • Java 5-1、用户模块-Mybatis代码生成

    5 1 用户模块 Mybatis代码生成 从这里开始 环境相关配置就告一段落了 项目就开始进入开发学习阶段 一 代码生成 实体类 Mapper接口 Mapper xml 分别生成 AppUser java SysUser java 再抽取B
  • Python题目:学生信息管理系统-高级版(图形界面+MySQL数据库)

    Python题目 学生信息管理系统 高级版 图形界面 MySQL数据库 使用图形界面显示 选用list tuple dictionary或map等数据结构 操作数据库存储X个学生的三门课的成绩 机器学习 Python程序设计 研究生英语 并
  • #BDA#笔记#阶段一:熟悉要分析的数据

    学习参考 1 小灶能力派 BDA证书班
  • java jhat_java命令--jhat命令使用

    jhat也是jdk内置的工具之一 主要是用来分析java堆的命令 可以将堆中的对象以html的形式显示出来 包括对象的数量 大小等等 并支持对象查询语言 使用jmap等方法生成java的堆文件后 使用其进行分析 第一步 导出堆 jmap d
  • 将一组很大的数据集随机分成两组数据

    最近在看机器学习的东西时发现了一些特别好玩的东西 机器学习中又分为训练集和测试集 如何把一组很大的数据分为这两个集合呢 可以使用接下来的函数完成 当然由于random这个随机数生成函数每次产生的数不一定都是刚好达到你的期望 所以总会有一点小
  • 【大数据入门核心技术-Impala】(一)Impala简介

    目录 一 Impala介绍 二 Impala优势 三 Impala主要功能 一 Impala介绍 Impala是Cloudera公司主导开发的新型查询系统 它提供SQL语义 能查询存储在Hadoop的HDFS和HBase中的PB级大数据 已
  • hibernateCRUD

    本文章的目的是实现hibernateDao层功能 但是具体的操作不在Dao层内完成 实体类 package com hibernate entity public class User private int id private Stri
  • 前端实现单元测试(代码版)

    Jest使用 下载 npm install save dev jest ts jest ts node jest globals types jest 在nodejs中支持ts ts执行报错 npx ts jest config init
  • 小白学习go之基础篇2 -- Slice切片的原理

    文章目录 前言 一 为什么要有切片 二 切片是怎么实现的呢 1 Go的切片结构体 SliceHeader 2 初始化切片的两种方式 3 切片是在栈上分配内存的还是在堆 4 切片的扩容 三 切片的使用有什么坑需要注意呢 总结 前言 本文主要记
  • 【解决】mysql安装时,Unable to connect to any of the specified MySQL hosts

    解决方法 win r打开运行 输入regedit 打开注册表 更改注册表 找到HKEY LOCAL MACHINE SYSTEM CurrentControlSet services mysql 服务名 ImagePath 我原先的地址为
  • 性能调优篇07:Zabbix性能优化的几点原则

    性能调优 概述 使Zabbix系统正确调整以获得最佳性能是非常重要的 Zabbix性能优化的几点原则 确保zabbix内部组件性能处于被监控状态 调优的基础 使用硬件性能足够好的服务器 不同角色分开 使用各自独立的服务器 使用分布式部署 调
  • 统计:Flutter,开发采用量

    本文作者 徐宜生 原文发布于 群英传 Flutter这个东西出来这么久了 到底市场占有率怎么样呢 为了让大家了解这一真实数据 也为了让大家了解当前Flutter在各大App中的使用情况 我今天下载了几百个App 占了手机将近80G空间 就为
  • 队列同步器AQS原理分析及具体实现

    Java中的并发编程很多都是以队列同步器AbstractQueuedSynchronizer为基础的 例如ReentrantLock CountDownLatch等 下面介绍其构成以及相应的实现 构成 private volatile in