深入理解 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.mzph.cn/diannao/6557.shtml

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

相关文章

使用FastGPT+OneAPI在本地使用Llama3

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统,提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景!他的重要特点就是工作流编排。 工作流编排:基于 Flow 模块的工作…

微信小程序 uniapp家庭食谱菜谱食材网上商城系统小程序ko137

随着生活节奏的不断加快,越来越多的人因为工作忙而没有时间自己出去订购喜欢的菜品。随着Internet的飞速发展,网络已经成为我们日常生活中必不可少的部分,越来越多的人也接受了电子商务这种快捷、方便的交易方式。网上订餐其独有的便捷性和直…

口才训练:如何用声音和语言展现自我魅力

口才训练:如何用声音和语言展现自我魅力 这里有一篇1270字左右的文章,主要介绍如何用声音和语言来展现自我魅力: 口才训练是提升个人魅力的重要途径之一。魅力不仅取决于外表,更重要的是声音和语言的运用。良好的语言表达能力可以…

Spring扩展点(一)Bean生命周期扩展点

Bean生命周期扩展点 影响多个Bean的实例化InstantiationAwareBeanPostProcessorBeanPostProcessor 影响单个Bean的实例化纯粹的生命周期回调函数InitializingBean(BeanPostProcessor 的before和after之间调用)DisposableBean Aware接口在生命周期实例化过…

二叉树的实现(详解,数据结构)

目录 一,二叉树需要实现的功能 二,下面是各功能详解 0.思想: 1.创建二叉树结点: 2.通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 3.二叉树销毁: 4.前序遍历: 5.中序遍历:…

RabbitMQ之顺序消费

什么是顺序消费 例如:业务上产生者发送三条消息, 分别是对同一条数据的增加、修改、删除操作, 如果没有保证顺序消费,执行顺序可能变成删除、修改、增加,这就乱了。 如何保证顺序性 一般我们讨论如何保证消息的顺序性&…

3GPP官网下载协议步骤

1.打开官网 https://www.3gpp.org/ 2.点击 3.在界面选择要找的series,跳转到查找界面 以V2X通信协议为例,论文中通常会看到许多应用: [7] “Study on evaluation methodology of new Vehicle-to-Everything (V2X) use cases for LTE and NR…

3.2Java全栈开发前端+后端(全栈工程师进阶之路)-前端框架VUE3框架-企业级应用- Vuex

Vuex简介 Vuex概述 Vuex是一个专门为Vue.js应用程序开发的状态管理模式, 它采用集中式存储管理所有组件的公共状态, 并以相应的规 则保证状态以一种可预测的方式发生变化. 试想这样的场景, 比如一个Vue的根实例下面有一个根组件名为App.vue, 它下面有两个子组件A.vue和B.vu…

022、Python+fastapi,第一个Python项目走向第22步:ubuntu 24.04 docker 安装mysql8集群、redis集群(三)

这次来安装mysql8了,以前安装不是docker安装,这个我也是第一次,人人都有第一次嚒 前言 前面的redis安装还是花了点时间的,主要是网上教程,各有各的好,大家千万别取其长处,个人觉得这个环境影响…

ASP.NET网上车辆档案管理系统

摘 要 本文采用基于Web的Asp.net技术,并与sql server 2000数据库相结合,研发了一套车辆档案管理系统。该系统扩展性好,易于维护。简化了车辆档案设计流程,去除了冗余信息。汽车销售企业可以通过本系统完成整个销售及售后所有档案…

python爬虫实战

import requests import json yesinput(输入页数:) yesint(yes)headers {"accept": "application/json, text/plain, */*","accept-language": "zh-CN,zh;q0.9","content-type": "application/json",…

一对一WebRTC视频通话系列(三)——leave和peer-leave信令实现

本篇博客主要分为两部分,第一部分为leave信令的实现,即当有客户端离开房间后,服务端和其他在房间内的客户需知晓。第二部分为媒体协商和网络协商相关API。 本系列博客主要记录一对一WebRTC视频通话实现过程中的一些重点,代码全部进…

渗透之sql盲注(时间/boolean盲注)

sql盲注:sql盲注意思是我们并不能在web页面中看到具体的信息,我们只能通过输入的语句的真假来判断。从而拿到我们想要的信息。 我们通常使用ascii值来进行盲注。 目录 手动注入: 时间盲注: 布尔盲注: python脚本注…

【Java】基本程序设计结构(一)

前言:现在,假定已经成功安装了JDK,并且能够运行上篇示例程序。本篇将开始介绍Java程序中的基本设计结构,其中包括:一个简单的Java应用,注释,数据类型,变量与常量,运算符&…

【深度学习基础(3)】初识神经网络之深度学习hello world

文章目录 一. 训练Keras中的MNIST数据集二. 工作流程1. 构建神经网络2. 准备图像数据3. 训练模型4. 利用模型进行预测5. (新数据上)评估模型精度 本节将首先给出一个神经网络示例,引出如下概念。了解完本节后,可以对神经网络在代码上的实现有一个整体的了…

【架构系列】RabbitMQ应用场景及在实际项目中如何搭建可靠的RabbitMQ架构体系

作者:后端小肥肠 创作不易,未经允许禁止转载。 1. 前言 RabbitMQ,作为一款高性能、可靠的消息队列软件,已经成为许多企业和开发团队的首选之一。它的灵活性和可扩展性使得它适用于各种应用场景,从简单的任务队列到复杂的分布式系统…

算法设计与分析——期末1h

目录 第一章 算法的定义 算法的三要素 算法的基本性质 算法的时间复杂度数量级: 第二章 兔子繁殖问题(递推法) 猴子吃桃问题(递推法) 穿越沙漠问题(递推法(倒推)) 百钱百…

解决Maven本地仓库存在依赖包还需要远程下载的问题

背景 公司有自己maven私服,正在在私服可以使用的情况,打包是没问题的。但是这次是由于公司大楼整体因电路检修而停电,所有服务器关机,包括maven私服服务器。然后当天确有一个包需要打,这个时候发现死活打不了&#xf…

线性数据结构-手写链表-LinkList

为什么需要手写实现数据结构? 其实技术的本身就是基础的积累和搭建的过程,基础扎实 地基平稳 万丈高楼才会久战不衰,做技术能一通百,百通千就不怕有再难得技术了。 一:链表的分类 主要有单向,双向和循环链表…

飞书API(7):MySQL 入库通用版本

一、引入 在上一篇介绍了如何使用 pandas 处理飞书接口返回的数据,并将处理好的数据入库。最终的代码拓展性太差,本篇来探讨下如何使得上一篇的最终代码拓展性更好!为什么上一篇的代码拓展性太差呢?我总结了几点: 列…