深入理解 Java 并发:AbstractQueuedSynchronizer 源码分析

序言

在多线程编程中,同步机制是保障线程安全和协调线程之间操作顺序的重要手段。AQS 作为 Java 中同步机制的基础框架,为开发者提供了一个灵活且高效的同步工具。本文将通过对 AQS 源码的分析,解读 AQS 的核心实现原理,并深入探讨其在不同同步组件中的应用。

一、什么是 AQS

workspace (4).jpg

AQS 是 AbstractQueuedSynchronizer 的缩写,是 Java 并发包中提供的一个用于实现各种锁和同步器的基础框架。它提供了一种灵活而强大的机制,可以帮助开发者实现各种自定义的锁和同步组件。

AQS 的设计思想是基于等待队列(Wait Queue)和同步状态(Sync State)。它通过维护一个等待队列来管理等待获取同步资源的线程,并使用一个同步状态来表示当前锁或同步器的状态。AQS 提供了一系列方法来操作同步状态和等待队列,包括获取同步状态、释放同步状态、加入等待队列等操作,以及唤醒等待队列中的线程等操作。

AQS 主要包含两个核心方法:tryAcquire 和 tryRelease。这两个方法由具体的同步器实现,用于控制同步状态的获取和释放。通过这两个方法的协作,AQS 可以实现各种类型的锁和同步器,包括独占锁、共享锁、读写锁、信号量等。

AQS 提供了一种基于条件等待和通知的高级线程同步机制,能够满足各种复杂的并发编程需求。它被广泛应用于 Java 并发编程中的各种锁和同步组件的实现,如 ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore 等。 AQS 提供了一个灵活、高效和可扩展的框架,使得开发者可以轻松地实现自定义的并发控制组件,并充分发挥 Java 并发包提供的丰富功能。

二、AQS 源码结构分析

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {// 构造器protected AbstractQueuedSynchronizer() { }// Node 内部类static final class Node {}// 构造链表等待队列的头节点private transient volatile Node head;// 构造链表等待队列的尾节点private transient volatile Node tail;// 同步状态private volatile int state;// tryAcquire 方法protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}// tryRelease 方法protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();}// ConditionObject 内部类public class ConditionObject implements Condition, java.io.Serializable {}// 省略了其他方法或属性}

上面是 AbstractQueuedSynchronizer 抽象类的源码,省略了一部分内容,保留了较为核心的内容,我们可以看出其核心结构如下:

  1. 同步状态(state):AQS 内部维护了一个同步状态变量,通常是一个整数,用于表示同步器的状态。这个同步状态是 AQS 控制同步和并发访问的核心。
  2. 等待队列(Wait Queue):AQS 使用等待队列来管理等待获取同步资源的线程。等待队列通常是一个基于 FIFO(先进先出)的数据结构(使用链表实现),确保等待线程的公平竞争和顺序执行。
  3. Node 类:等待队列中的每个节点都是 Node 类的实例,它包含了线程的相关信息以及用于构建队列的链接信息。Node 类中通常包含了指向前驱节点和后继节点的引用,用于构建双向链表。
  4. 核心方法:acquire 和 release:用于获取和释放同步状态的方法,是 AQS 中最核心的方法之一。tryAcquire 和 tryRelease:尝试获取和释放同步状态的方法,是具体同步器需要实现的抽象方法。
  5. 条件对象(ConditionObject):条件对象是 AQS 提供的一种机制,用于管理条件队列。

三、AQS 工作原理

workspace (2).jpg

AQS 的核心思想是基于状态的抽象和等待队列的管理。具体来说,当一个线程尝试获取锁或者同步资源时,如果失败了,它会被包装成一个节点加入到等待队列中,然后自旋等待获取锁。当锁的释放动作发生时,AQS 会按照特定的规则选择一个节点来唤醒,从而实现线程的竞争和同步。

四、AQS 关键方法解析

4.1 acquire() 方法

public final void acquire(int arg) {
if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}

acquire 是 AQS 定义获取锁的方法。该方法看似简单其实做了许多事情。首先,尝试使用 tryAcquire 方法来快速尝试获取同步状态,如果失败则将当前线程加入到等待队列中,并自旋等待直到成功获取同步状态为止。如果线程在等待过程中被中断,则会自我中断。

acquire 方法被 final 关键字修饰了,是不可被重写的。acquire 方法其实只是定义了流程,具体逻辑是下放给 tryAcquire 方法了。

protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

从上面的源码可以看出 tryAcquire 方法没有具体的实现,需要交由其子类实现。

4.2 release() 方法

public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {// 获取当前等待队列的头节点Node h = head;// 检查头节点是否存在且其等待状态不为 0if (h != null && h.waitStatus != 0)// 唤醒后继节点unparkSuccessor(h);// 释放锁成功,返回 truereturn true;
}
// 释放锁失败,返回 false
return false;
}

release 是 AQS 定义释放锁的方法。该方法与获取锁的 acquire 方法类似,只是定义了释放锁的流程,具体的释放逻辑是交由子类去实现 tryRelease 方法。

五、AQS 深入分析

等待队列与部分核心方法上文已经有过解释了。AQS 剩下的核心内容还有:

  1. 同步状态(state)
  2. Node 类
  3. 条件对象(ConditionObject)

image.png

同步状态是一个整数变量,通常被命名为 state。同步状态用于表示当前同步器的状态信息,可以被不同类型的同步器用于不同的目的。具体来说,同步状态可以代表某种资源的数量、某种资源的可用性或者某种锁的状态等。例如,在独占锁(Exclusive Lock)中,同步状态可以表示锁的占用情况;在共享锁(Shared Lock)中,同步状态可以表示锁的共享数量;在 Semaphore(信号量)中,同步状态可以表示可用的许可数量等。

Node 类是 AQS 中的一个内部类,主要用于构建链表形式的等待队列。每个节点都代表一个等待获取同步资源的线程。

在并发编程中,等待唤醒机制是一种重要的线程同步机制,用于实现线程之间的协作和通信。而 AQS 提供了条件队列用于实现等待唤醒机制,条件队列通常与条件对象一起使用,条件对象是 AQS 提供的一种机制,用于管理条件队列

六、AQS 在锁和同步组件中的应用

AQS 只是一个抽象类,我们想要使用它必须要实现相关的逻辑。而 Java 基于 AQS 已经给我们提供了许多的实现:

  1. ReentrantLock(重入锁):ReentrantLock 是 Java 并发包提供的一种独占锁实现,它使用了 AQS 来实现锁的底层逻辑。ReentrantLock 内部持有一个 AQS 实例,通过 AQS 的 acquire 和 release 方法来控制锁的获取和释放,从而实现可重入性和线程安全性。
  2. ReentrantReadWriteLock(重入读写锁):ReentrantReadWriteLock 是 Java 并发包提供的一种读写锁实现,它也使用了 AQS 来实现锁的底层逻辑。ReentrantReadWriteLock 内部维护了两个 AQS 实例,分别用于读锁和写锁的管理。通过 AQS 的 acquire 和 release 方法来控制读锁和写锁的获取和释放,从而实现读写锁的功能。
  3. CountDownLatch(倒计时门栓):CountDownLatch 是 Java 并发包提供的一种同步工具,它使用了 AQS 来实现同步等待的逻辑。CountDownLatch 内部持有一个 AQS 实例,通过 AQS 的 acquire 和 release 方法来实现等待和通知的功能。线程调用 await 方法等待计数器归零,而其他线程调用 countDown 方法来减少计数器的值,当计数器归零时,等待的线程被唤醒继续执行。
  4. Semaphore(信号量):Semaphore 是 Java 并发包提供的一种同步工具,它也使用了 AQS 来实现信号量的逻辑。Semaphore 内部持有一个 AQS 实例,通过 AQS 的 acquire 和 release 方法来控制信号量的获取和释放。线程调用 acquire 方法尝试获取信号量,而其他线程调用 release 方法来释放信号量,从而控制同时访问资源的数量。

推荐阅读

  1. 深入了解 Arthas:Java 应用程序诊断利器
  2. 基于 AI 的数据库助手-Chat2DB
  3. EasyExcel 处理 Excel
  4. 实体映射解决方案-MapStruct
  5. 动态切换数据源的最佳实践

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

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

相关文章

web3风格的网页怎么设计?分享几个,找找感觉。

web3风格的网站是指基于区块链技术和去中心化理念的网站设计风格。这种设计风格强调开放性、透明性和用户自治,体现了Web3的核心价值观。 以下是一些常见的Web3风格网站设计元素: 去中心化标志:在网站的设计中使用去中心化的标志&#xff0…

ElasticSearch教程入门到精通——第二部分(基于ELK技术栈elasticsearch 7.x新特性)

ElasticSearch教程入门到精通——第二部分(基于ELK技术栈elasticsearch 7.x新特性) 1. JavaAPI-环境准备1.1 新建Maven工程——添加依赖1.2 HelloElasticsearch 2. 索引2.1 索引——创建2.2 索引——查询2.3 索引——删除 3. 文档3.1 文档——重构3.2 文…

OpenCV 实现重新映射(53)

返回:OpenCV系列文章目录(持续更新中......) 上一篇:OpenCV 实现霍夫圆变换(52) 下一篇 :OpenCV实现仿射变换(54) 目标 在本教程中,您将学习如何: 一个。使用 OpenCV 函数 cv::remap 实现简…

阿里低代码引擎学习记录

官网 一、关于设计器 1、从设计器入手进行低代码开发 设计器就是我们用拖拉拽的方法,配合少量代码进行页面或者应用开发的在线工具。 阿里官方提供了以下八个不同类型的设计器Demo: 综合场景Demo(各项能力相对完整,使用Fusion…

Gitea 上传用户签名

在 Gitea 的用户管理部分,有一个 SSH 和 GPG 的选项。 单击这个选项,可以在选项上添加 Key。 Key 的来源 如是 Windows 的用户,可以选择 Kleopatra 这个软件。 通过这个软件生成的 Key 的界面中有一个导出功能。 单击这个导出,…

区块链 | IPFS:Merkle DAG

🦊原文:IPFS: Merkle DAG 数据结构 - 知乎 🦊写在前面:本文属于搬运博客,自己留存学习。 1 Merkle DAG 的简介 Merkle DAG 是 IPFS 系统的核心概念之一。虽然 Merkle DAG 并不是由 IPFS 团队发明的,它来自…

【UnityRPG游戏制作】Unity_RPG项目_玩家逻辑相关

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:就业…

自动驾驶-第02课软件环境基础(ROSCMake)

1. 什么是ros 2. 为什么使用ros 3. ROS通信 3.1 Catkin编译系统

MT3608B 航天民芯代理 1.2Mhz 24V输入 升压转换器

深圳市润泽芯电子有限公司为航天民芯一级代理商 技术支持欢迎试样~Tel:18028786817 简述 MT3608B是恒定频率的6针SOT23电流模式升压转换器,用于小型、低功耗应用。MT3608B开关频率为1.2MHz,允许使用微小、低电平成本电容器和电感器高度不…

正版Office-Word使用时却提示无网络连接请检查你的网络设置 然后重试

这是购买电脑时自带的已经安装好的word。看纸箱外壳有office标记,但是好像没有印系列号。 某天要使用。提示:无网络连接请检查你的网络设置。 经过网上高手的提示: 说要勾选勾选ssl3.0、TLS1.0、1.1、1.2。 我的截图 我电脑进去就缺1.2. …

Go协程的底层原理(图文详解)

为什么要有协程 什么是进程 操作系统“程序”的最小单位进程用来占用内存空间进程相当于厂房,占用工厂空间 什么是线程 进程如果比作厂房,线程就是厂房里面的生产线: 每个进程可以有多个线程线程使用系统分配给进程的内存,线…

深度学习之基于Vgg16卷积神经网络印度交警手势识别系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着智能交通系统的不断发展,手势识别技术在其中扮演着越来越重要的角色。特别是在印度等…

Golang | Leetcode Golang题解之第58题最后一个单词的长度

题目: 题解: func lengthOfLastWord(s string) (ans int) {index : len(s) - 1for s[index] {index--}for index > 0 && s[index] ! {ansindex--}return }

AST原理(反混淆)

一、AST原理 jscode var a "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";在上述代码中,a 是一个变量,它被赋值为一个由 Unicode 转义序列组成的字符串。Unicode 转义序列在 JavaScript 中以 \u 开头,后跟四个十六进…

JavaScript 如何理解柯里化函数结构及调用

文章目录 柯里化函数是什么逐步理解柯里化函数 柯里化函数是什么 柯里化(Currying)函数,又称部分求值,是一种函数转换技术。这种技术将一个接受多个参数的函数转换为一系列接受单一参数的函数。具体来说,一个柯里化的…

LWIP+TCP客户端

一、TCP API函数 其中tcp_poll()函数的第三个参数表示隔几秒调用一次这个周期性函数 二、修改服务器的IP 三、TCP客户端编程思路 申请套接字绑定服务器IP和端口号等待客户端连接 进入连接回调函数在连接回调函数中 配置一些回调函数,如接收回调函数,周期…

C语言 基本数据类型及大小

一、基本数据类型 1.整型int 整型的关键字是int,定义一个整型变量时,只需要用int来修饰即可。也分为短整型和长整型。 2.浮点型 浮点型又分单精度浮点型float和双精度浮点型double。 3.字符型char 前面的整型和浮点型都是用于存放数字。字符型&…

【python的魅力】:教你如何用几行代码实现文本语音识别

文章目录 引言一、运行效果二、文本转换为语音2.1 使用pyttsx32.2 使用SAPI实现文本转换语音2.3 使用 SpeechLib实现文本转换语音 三、语音转换为文本3.1 使用 PocketSphinx实现语音转换文本 引言 语音识别技术,也被称为自动语音识别,目标是以电脑自动将…

用LangChain打造一个可以管理日程的智能助手

存储设计定义工具创建llm提示词模板创建Agent执行总结 众所周知,GPT可以认为是一个离线的软件的,对于一些实时性有要求的功能是完全不行,比如实时信息检索,再比如我们今天要实现个一个日程管理的功能,这个功能你纯依赖…

Hdfs小文件治理策略以及治理经验

小文件是 Hadoop 集群运维中的常见挑战,尤其对于大规模运行的集群来说可谓至关重要。如果处理不好,可能会导致许多并发症。Hadoop集群本质是为了TB,PB规模的数据存储和计算因运而生的。为啥大数据开发都说小文件的治理重要,说HDFS 存储小文件…