Java 入门指南:Java 并发编程 —— AQS、AQLS、AOS 锁与同步器的框架

news2024/9/21 14:33:44

AQS

AQSAbstractQueuedSynchronizer 的缩写,即 抽象队列同步器,是 Java.util.concurrent 中的一个基础工具类,用于实现同步器(Synchronizer)的开发。

AQS 提供了一种实现锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的同步器,使得开发者能够更方便地编写线程安全的代码。

AQS 的使用涉及到复杂的同步机制和多线程协调,需要仔细考虑并发控制的细节。通常情况下,直接使用基于 AQS 实现的高级同步器(如 ReentrantLockSemaphoreReentrantReadWriteLockSynchronousQueueFutureTaskCountDownLatch 等)更为常见,而不需要直接使用 AQS。

AQS 的数据结构

AQS 内部使用了一个先进先出(FIFO)的 双端队列,被称为,被称为 CLH 队列(Craig, Landin, and Hagersten queues)。它并不直接储存线程,而是储存拥有线程的 Node 节点

使用了两个虚拟的引用 headtail 用于标识队列的头部和尾部,管理等待线程。

它的实现方式和普通的双端队列略有不同,主要涉及到以下两个方面:

  1. 节点的处理方式:在 CLH 队列中,每个 Node 都代表了一个等待线程,同时它还包含一个 state 属性。

    当一个线程尝试获取同步状态时,它会创建一个节点插入到 CLH 队列的尾部,然后自旋等待获取同步状态。当同步状态可用时,AQS 会使用 CAS 操作将当前头结点改为自己,并返回原头结点,从而唤醒自己。

  2. 队列的管理方式:CLH 队列采用的是轻量级锁的思路,当一个线程到达尾节点时它只需自旋等待即可。此时,节点的 state 属性会发挥重要作用,它能够指示当前节点是处于等待还是已经被唤醒出队并不再使用,从而避免了队列中节点的浪费。

![[Craig, Landin, and Hagersten queues.png]]

AQS 内部使用了一个 [[JMM Java内存模型#volatile|volatile]] 的变量 state 来作为资源的标识。同时定义了几个获取和改变 state 的 [[Protected]] 方法 getState()setState()compareAndSetState(),均为原子操作,子类可以覆盖这些方法来实现自己的逻辑。

compareAndSetState 的实现依赖于 [[实现线程安全#Unsafe 类|Unsafe]] 的 compareAndSwapInt() 方法

线程的 Node 节点

![[AQS Node Source Code.png]]

waitStatus 用来标记当前节点的状态:

  • CANCELLED:表示当前节点(对应的线程)已被取消。当等待超时或被中断,会触发进入为此状态,进入该状态后节点状态不再变化;

  • SIGNAL:后面节点等待当前节点唤醒;

  • CONDITIONCondition 类 中使用,当前线程阻塞在 Condition,如果其他线程调用了 Condition 的 signal 方法,这个节点将从等待队列转移到同步队列队尾,等待获取同步锁;

  • PROPAGATE:共享模式,前置节点唤醒后面节点后,唤醒操作无条件传播下去;

  • 0:中间状态,当前节点后面的节点已经唤醒,但是当前节点线程还没有执行完成。

实现队列

通过 Node 可以实现两种队列:

  1. 通过 prev 和 next 实现 CLH 队列。

    在 CLH 锁中,每个等待的线程都会有一个关联的 Node,每个 Node 有一个 prev 和 next 指针。当一个线程尝试获取锁并失败时,它会将自己添加到队列的尾部并自旋,等待前一个节点的线程释放锁。

![[CLTLock instance.png]]

  1. 通过 nextWaiter 实现 Condition 上的等待线程队列(单向队列),这个 Condition 主要用在 ReentrantLock 类中

常用方法

AQS 主要提供了以下几个重要方法(均为 protected):

  • isHeldExclusively():该线程是否正在独占资源。只有用到 Condition 类才需要去实现它。

  • acquire(int arg):尝试获取资源,如果获取失败则等待,直到成功获取同步状态或被中断。

  • release(int arg):释放资源,唤醒等待队列中的其他线程。

  • tryAcquire(int arg):独占方式,尝试获取资源,成功返回 true,失败返回 false。

  • tryRelease(int arg):独占方式,尝试释放资源,成功返回 true,失败返回 false。

  • tryAcquireShared(int arg):共享方式。尝试获取资源。负数表示失败;0 表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

  • tryReleaseShared(int arg):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回 true,否则返回 false。

arg 为要 获取/释放 的资源个数,在独占模式下始终为 1

这些方法虽然都是 protected 的,但是它们并没有在 AQS 具体实现,而是直接抛出异常:

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

这里不使用抽象方法的目的是:避免强迫子类中把所有的抽象方法都实现一遍,减少无用功,这样子类只需要实现自己关心的抽象方法即可,如 信号 Semaphoreopen 只需要实现 tryAcquire 方法而不用实现其余不需要用到的模版方法

设计思想

AQS 的设计思想是基于模板方法模式,它将同步器的核心操作定义为抽象方法,由子类去实现具体细节。AQS 内部维护了一个等待队列,用于管理等待获取同步状态的线程。

AQS 通过 CAS(Compare and Swap)操作、队列等待、线程阻塞等机制来实现线程的协作和同步。

获取资源

获取资源的入口是 acquire(int arg) 方法

![[AQS accquire source code.png]]

首先调用 tryAcquire 尝试去获取资源,如果获取资源失败,就通过 addWaiter(Node.EXCLUSIVE) 方法把这个线程插入到等待队列(在队列的尾部插入新的 Node 节点)中。其中传入的参数代表要插入的 Node 是独占式的。通过 CAS 自旋的方式保证操作的线程安全性。

之后,处于等待队列的 Node 从头结点一个一个去获取资源。

![[AQS acquire resource.png]]

此外,还可以通过如下方法获取资源:

  • acquireInterruptibly:申请可中断(在线程中断时可能会抛出InterruptedException)的资源(独占模式)

  • acquireShared:申请共享模式的资源

  • acquireSharedInterruptibly:申请可中断的资源(共享模式)

释放资源

释放资源相比于获取资源要简单得多

java.util.concurrent.locks.ReentrantLock 的实现中,tryRelease(arg) 会减少持有锁的数量,如果持有锁的数量变为0,释放锁并返回 true。

如果 tryRelease(arg) 成功释放了锁,那么接下来会检查队列的头结点。如果头结点存在并且 waitStatus 不为0(这意味着有线程在等待),那么会调用 unparkSuccessor(Node h) 方法来唤醒等待的线程。

AQS实现自定义锁

下面是一个简单的示例,展示如何使用 AQS 来实现一个自定义的可重入锁

这个简单的锁实现没有考虑重入性和公平性等问题,实际应用中可能还需要更复杂的逻辑来满足更高级的需求。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class CustomReentrantLock implements Lock {

    private final Sync sync = new Sync();

    private static final class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            return compareAndSetState(0, 1);
        }

        @Override
        protected boolean tryRelease(int arg) {
            setState(0);
            return true;
        }

        Condition newCondition() {
            return new ConditionObject();
        }
    }

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

代码说明:

  • CustomReentrantLock 类实现了 Lock 接口,提供了锁的基本操作。

  • Sync 类继承自 AbstractQueuedSynchronizer,并重写了 tryAcquiretryRelease 方法来控制锁的状态。

    • tryAcquire: 尝试获取锁,通过 compareAndSetState 方法原子地更新状态值。如果当前状态为0(表示锁未被持有),则设置状态为1并返回true,否则返回false
    • tryRelease: 释放锁,简单地将状态设置为0。
  • lock/unlock 方法: 使用 sync 实例来调用 AbstractQueuedSynchronizer 中定义的 acquirerelease 方法。

  • Condition: 通过 newCondition 方法创建条件变量。

AQLS

AQS 里面的“资源”是用一个 int 类型的数据来表示的,有时候业务需求的资源数超出了 int 的范围,所以在 JDK 1.6 中,多了一个 AQLS(AbstractQueuedLongSynchronizer)。它的代码跟 AQS 几乎一样,只是把资源的类型变成了 long 类型。

AOS

AQSAQLS 都继承了一个类 AOS(AbstractOwnableSynchronizer)。这个类于 JDK 1.6 引入。用于表示锁与持有者之间的关系(独占模式)

![[AbstractOwnableSynchronizer.png]]

  • 字段

    • private transient Thread exclusiveOwnerThread;:这个字段用于存储当前独占访问权的线程。由于它是transient的,因此在序列化时不会被保存。
  • 方法

    • protected final void setExclusiveOwnerThread(Thread thread);:此方法用于设置当前拥有独占访问权的线程。如果传入null,则表示没有线程拥有访问权。

    • protected final Thread getExclusiveOwnerThread();:此方法返回最后通过setExclusiveOwnerThread方法设置的线程,如果没有设置过,则返回null

AbstractOwnableSynchronizer 提供了一种机制来允许线程独占某个资源或同步器,通常被用作创建锁和其他同步器的基类。例如,在Java的 ReentrantLock 类中,就使用了 AbstractOwnableSynchronizer 来跟踪哪个线程当前拥有锁。这使得 ReentrantLock 能够支持重入性,即同一个线程可以多次获得同一个锁

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

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

相关文章

Mysql高级篇(上)

Mysql高级篇(上) Mysql架构介绍(一)1、Linux环境下的MySQL的安装与使用2、MySQL请求到响应字符集变化(了解)3、MySQL8 的主要目录结构4、数据库和文件系统关系(1)默认数据库(2)数据库…

C语言程序设计之基础易错题锦集6

C语言程序设计之基础易错题锦集6 问题 6_0解析 6_0 问题 6_0 将形参 s 所指字符串中字母字符顺序前移,其他字符顺序后移,处理后将新字符串的首地址作为函数值返回。 例如:输入 :“asd123fgh456df”, 输出:“a…

React基础面试题

React 面试题 以下是面试官最有可能问到的 50 个 React 面试题和答案。为方便你学习,我对它们进行了分类: 基本知识React 组件React ReduxReact 路由 基本知识 1. 区分Real DOM和Virtual DOM Real DOMVirtual DOM1. 更新缓慢。1. 更新更快。2. 可以…

简化理解:Tomcat 和 Servlet 规范

有时候,我们会把复杂的技术概念弄得很复杂,其实这些东西可以用更简单的语言来理解。我们来看看 Tomcat 和 Servlet 规范到底是怎么回事。 1. 什么是 Servlet 规范? 简单来说,Sun 公司(现在是 Oracle)定了…

YOLOv9改进策略【模型轻量化】| MoblieNetV3:基于搜索技术和新颖架构设计的轻量型网络模型

一、本文介绍 本文记录的是基于MobileNet V3的YOLOv9目标检测轻量化改进方法研究。MobileNet V3的模型结构是通过网络搜索得来的,其中的基础模块结合了MobileNet V1的深度可分离卷积、MobileNet V2的线性瓶颈和倒置残差结构以及MnasNet中基于挤压和激励的轻量级注意…

注意力机制(Attention mechanism)(上篇)

在图像识别的时候,假设输入的图像大小都是一样的。但如果问题变得复杂,如图1所 示,输入是一组向量,并且输入的向量的数量是会改变的,即每次模型输入的序列长度都不一 样,这个时候应该要怎么处理呢&#xff…

随笔十、音频扩展模块测试

本项测试简单,对购买的音频扩展模块进行录音放音测试 按照使用说明,连接音频小板,一个喇叭一个麦克风,4根线,buildroot系统镜像 录音测试 rootRK356X:/# arecord -c 1 -r 44100 -f S16_LE /tmp/record.wav Recording …

Java-多线程入门

多线程是指在软件或硬件上实现多个线程并发执行的技术。为了更好地理解多线程,首先需要了解几个基本概念: 了解概念 1.程序 程序是为完成特定任务、用某种语言编写的一组指令的集合。它是一个静态的概念,通常存储在磁盘或其他非易失性存储器…

vxe-table 更新到最新版本

当前版本: "vxe-table": "^4.3.0-beta.3" 更新后: "vxe-table": "^4.7.75" 需要调整代码: 更改前main.js 更改后:

Jenkins Environment Injector Plugin 插件详解

引言 在做自动化测试的过程中,我们需要经常发送测试报告给相关研发、产品和上级,但是Jenkins邮件模板不支持Javascritpt脚本来动态生成数据,只支持静态的HTML代码,那么我们就没有办法了吗?非也,我们可以通…

SQL进阶技巧:经典问题题-换座位

目录 0 问题描述 1 数据准备 2 问题分析 3 小结 0 问题描述 表 seat中有2个字段id和student id 是该表的主键(唯一值)列,student表示学生姓名。 该表的每一行都表示学生的姓名和 ID。 id 是一个连续的增量。 编写解决方案来交换每两个连续的学生的座位号。如果学生的数量…

Windows下Nacos安装与配置

目录 1. 下载Nacos 2. 解压安装包 3. 配置系统环境变量 4. 启动Nacos 5. 配置数据库为mysql 6. 配置鉴权默认值 1. 下载Nacos 我下载的版本是2.3.0。 下载地址:Nacos Server 下载 | Nacos 官网 但是我从官方那里下载超级慢,找了一个链接下载&#…

Milvus 向量数据库进阶系列丨构建 RAG 多租户/多用户系统 (下)

本系列文章介绍 在和社区小伙伴们交流的过程中,我们发现大家最关心的问题从来不是某个具体的功能如何使用,而是面对一个具体的实战场景时,如何选择合适的向量数据库解决方案或最优的功能组合。在 “Milvus 向量数据库进阶” 这个系列文章中&a…

Python将两个Excel文件按相同字段合并到一起

在工作中我们需要将两个有关联的数据文件合并成一个Excel 1. 创建两个excel文件 test1 test2 2. 使用Pandas 数据分析工具进行合并 Pandas 一个强大的分析结构化数据的工具集,提供了易于使用的数据结构和数据分析工具,特别适用于处理结构化数据&#x…

Linux操作系统软件管理

一.软件安装包类型 1.常见软件安装包格式 源码软件 .tar.gz,.tar.bz2 优点:从功能使用的角度来讲,比rpm软件安装包更加灵活, 比如 在使用源码软件安装包的时候,可以自行选择安装软件的目录,这样操作便…

TypeScript与vue

一、为组件的props标注类型 - 在没有使用TS之前,是这样接受props: - 在TS环境中,是这样接受props: - 对于props的可选项如何限制呢? 1、类型限制 类型限制在接收的时候就已经定义好了 2、可选属性(必填限制…

华媒舍:8个为什么要选择国外纳斯达克大屏推广的原因

1.纳斯达克大屏的知名度和美誉度纳斯达克大屏是全球有名气的金融业信息表明平台之一,它在全球金融体系有着广泛的知名度和美誉度。以在纳斯达克大屏中进行推广,能够让更多人关注与掌握推广具体内容,从而增加品牌曝光率。 2.纳斯达克大屏高客流…

代码随想录Day 31|leetcode题目:56.合并区间、738.单调递增的数字、968.监控二叉树

提示:DDU,供自己复习使用。欢迎大家前来讨论~ 文章目录 贪心算法Part05题目题目一:56. 合并区间解题思路 题目二:738.单调递增的数字解题思路:暴力解法:结果超时贪心算法 题目三: 968.监控二叉…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 9月1日,星期日

每天一分钟,知晓天下事! 2024年9月1日 星期日 农历七月廿九 1、 未来一周,四川东部、重庆等地持续高温天气,最高气温可达40~42℃。 2、 山西明确:今日起,职工医保个人账户家庭共济范围由直系亲…

QNN:基于QNN+example重构之后的yolov8det部署

QNN是高通发布的神经网络推理引擎,是SNPE的升级版,其主要功能是: 完成从Pytorch/TensorFlow/Keras/Onnx等神经网络框架到高通计算平台的模型转换; 完成模型的低比特量化(int8),使其能够运行在高…