广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java 通过AQS实现数据组织
  • 333
分享到

Java 通过AQS实现数据组织

2024-04-02 19:04:59 333人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

引言 从本篇文章开始,我们将介绍 Java AQS 的实现方式,本文先介绍 AQS 的内部数据是如何组织的,后面的文章中再分别介绍 AQS 的各个部门实现。 AQS 通过前面的介绍,

引言

从本篇文章开始,我们将介绍 Java AQS 的实现方式,本文先介绍 AQS 的内部数据是如何组织的,后面的文章中再分别介绍 AQS 的各个部门实现。

AQS

通过前面的介绍,大家一定看出来了,上述的各种类型的和一些线程控制接口(CountDownLatch 等),最终都是通过 AQS 来实现的,不同之处只在于 tryAcquire 等抽象函数如何实现。从这个角度来看,AQS(AbstractQueuedSynchronizer) 这个基类设计的真的很不错,能够包容各种同步控制方案,并提供了必须的下层依赖:比如阻塞,队列等。接下来我们就来揭开它神秘的面纱。

内部数据

AQS 顾名思义,就是通过队列来组织修改互斥资源的请求。当这个资源空闲时间,那么修改请求可以直接进行,而当这个资源处于锁定状态时,就需要等待,AQS 会将所有等待的请求维护在一个类似于 CLH 的队列中。CLH:Craig、Landin and Hagersten队列,是单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。主要原理图如下:

图中的 state 是一个用 volatile 修饰的 int 变量,它的使用都是通过 CAS 来进行的,而 FIFO 队列完成请求排队的工作,队列的操作也是通过 CAS 来进行的,正因如此该队列的操作才能达到理想的性能要求。

通过 CAS 修改 state 比较容易,大家应该都能理解,但是如果要通过 CAS 维护一个双向队列要怎么做呢?这里我们看一下 AQS 中 CLH 队列的实现。在 AQS 中有两个指针一个指针指向了队列头,一个指向了队列尾。它们都是懒初始化的,也就是说最初都为null。



private transient volatile node head;


private transient volatile Node tail;

队列中的每个节点,都是一个 Node 实例,该实例的第一个关键字段是 waitState,它表述了当前节点所处的状态,通过 CAS 进行修改:

  • SIGNAL:表示当前节点承担唤醒后继节点的责任
  • CANCELLED:表示当前节点已经超时或者被打断
  • CONDITioN:表示当前节点正在 Condition 上等待(通过锁可以创建 Condition 对象)
  • PROPAGATE:只会设置在 head 节点上,用于表明释放共享锁时,需要将这个行为传播到其他节点上,这个我们稍后详细介绍。

static final class Node {
    
    static final Node SHARED = new Node();
    
    static final Node EXCLUSIVE = null;

    
    static final int CANCELLED =  1;
    
    static final int SIGNAL    = -1;
    
    static final int CONDITION = -2;
    
    static final int PROPAGATE = -3;

    
    volatile int waitStatus;

    
    volatile Node prev;

    
    volatile Node next;

    
    volatile Thread thread;

    
    Node nextWaiter;

    
    final boolean isshared() {
        return nextWaiter == SHARED;
    }
    //...
}

因为是双向队列,所以 Node 实例中势必有 prev 和 next 指针,此外 Node 中还会保存与其对应的线程。最后是 nextWaiter,当一个节点对应了共享请求时,nextWaiter 指向了 Node. SHARED 而当一个节点是排他请求时,nextWaiter 默认指向了 Node. EXCLUSIVE 也就是 null。我们知道 AQS 也提供了 Condition 功能,该功能就是通过 nextWaiter 来维护在 Condition 上等待的线程。也就是说这里的 nextWaiter 在锁的实现部分中,扮演者共享锁和排它锁的标志位,而在条件等待队列中,充当链表的 next 指针。

同步队列

接下来,我们由最常见的入队操作出发,介绍 AQS 框架的实现与使用。从下面的代码中可以看到入队操作支持两种模式,一种是排他模式,一种是共享模式,分别对应了排它锁场景和共享锁场景。

  1. 当任意一种请求,要入队时,先会构建一个 Node 实例,然后获取当前 AQS 队列的尾结点,如果尾结点为空,就是说队列还没初始化,初始化过程在后面 enq 函数中实现
  2. 这里我们先看初始化之后的情况,即 tail != null,先将当前 Node 的前向指针 prev 更新,然后通过 CAS 将尾结点修改为当前 Node,修改成功时,再更新前一个节点的后向指针 next,因为只有修改尾指针过程是原子的,所以这里会出现新插入一个节点时,之前的尾节点 previousTail 的 next 指针为null的情况,也就是说会存在短暂的正向指针和反向指针不同步的情况,不过在后面的介绍中,你会发现 AQS 很完备地避开了这种不同步带来的风险(通过从后往前遍历)
  3. 如果上述操作成功,则当前线程已经进入同步队列,否则,可能存在多个线程的竞争,其他线程设置尾结点成功了,而当前线程失败了,这时候会和尾结点未初始化一样进入 enq 函数中。


private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        // 已经进行了初始化
        node.prev = pred;
        // CAS 修改尾节点
        if (compareAndSetTail(pred, node)) {
            // 成功之后再修改后向指针
            pred.next = node;
            return node;
        }
    }
    // 循环 CAS 过程和初始化过程
    enq(node);
    return node;
}

正常通过 CAS 修改数据都会在一个循环中进行,而这里的 addWaiter 只是在一个 if 中进行,这是为什么呢?实际上,大家看到的 addWaiter 的这部分 CAS 过程是一个快速执行线,在没有竞争时,这种方式能省略不少判断过程。当发生竞争时,会进入 enq 函数中,那里才是循环 CAS 的地方。

  1. 整个 enq 的工作在一个循环中进行
  2. 先会检查是否未进行初始化,是的话,就设置一个虚拟节点 Node 作为 head 和 tail,也就是说同步队列的第一个节点并不保存实际数据,只是一个保存指针的地方
  3. 初始化完成后,通过 CAS 修改尾节点,直到修改成功为止,最后修复后向指针


private Node enq(final Node node) {
    for (;;) {// 在一个循环中进行 CAS 操作
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            // CAS 修改尾节点
            if (compareAndSetTail(t, node)) {
                // 成功之后再修改后向指针
                t.next = node;
                return t;
            }
        }

以上就是通过AQS实现数据组织的详细内容,更多关于AQS数据组织的资料请关注编程网其它相关文章!

--结束END--

本文标题: Java 通过AQS实现数据组织

本文链接: https://www.lsjlt.com/news/123148.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java 通过AQS实现数据组织
    引言 从本篇文章开始,我们将介绍 Java AQS 的实现方式,本文先介绍 AQS 的内部数据是如何组织的,后面的文章中再分别介绍 AQS 的各个部门实现。 AQS 通过前面的介绍,...
    99+
    2022-11-12
  • java中如何通过数组实现队列
    数组实现队列方法如下:队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如上图,其中maxSize是队列的最大容量;队列的输入、输出分别从前后端处理,因此需要front和rear两个变量分别记录队列前后端的下标,其会随着...
    99+
    2022-01-06
    java入门 java 数组 队列
  • Java如何实现通过键盘输入一个数组
    目录如何通过键盘输入一个数组第一种方法:(不限制输入数组的长度)第二种方法:(限制输入的个数)不限制从键盘输入一个数组下面用二分查找举例如何通过键盘输入一个数组 有时候在编写Jave...
    99+
    2022-11-13
  • Java怎么实现通过键盘输入一个数组
    本篇内容介绍了“Java怎么实现通过键盘输入一个数组”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!如何通过键盘输入一个数组有时候在编写Jav...
    99+
    2023-06-29
  • 通过容器扩展属性IExtenderProvider实现WinForm通用数据验证组件
    大家对如下的Tip组件使用应该不陌生,要想让窗体上的控件使用ToolTip功能,只需要拖动一个ToolTip组件到窗口,所有的控件就可以使用该功能,做信息提示。 本博文要记录的,就是...
    99+
    2022-11-12
  • 通过数据结构实现简易通讯录
    AddressBookTest是测试类package MyADB;import java.util.InputMismatchException;import java.util.Scanner;class InstructionsMist...
    99+
    2023-06-02
  • 如何通过Java日志记录实现大数据分析?
    在现代软件开发中,日志记录是一个不可或缺的部分。它可以帮助开发人员快速找到和修复软件中的问题,同时也可以为后续的数据分析提供有价值的信息。在本文中,我们将介绍如何通过Java日志记录实现大数据分析。 一、什么是Java日志记录? Java...
    99+
    2023-09-25
    日志 并发 大数据
  • Java通过数据库表生成实体类详细过程
    目录项目背景项目代码使用说明配置相关swagger操作目前的缺点项目背景 最近在做的项目,涉及到数据库的操作了,之前做的是直接调用接口,不用做存库操作。 因此要增加大量特殊格式的实体...
    99+
    2023-02-07
    Java数据库表生成实体类 Java生成实体类
  • FineReport中树数据集怎么实现组织树报表
    FineReport中树数据集怎么实现组织树报表,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。 组织树报表中由id与父id来实现组织树报表,若层级数较多时,对每个单元格设置过滤...
    99+
    2023-06-03
  • JAVA如何通过使用数组遍历和if条件实现选择数据中的最大值
    这篇文章给大家分享的是有关JAVA如何通过使用数组遍历和if条件实现选择数据中的最大值的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。前言:通过使用数组遍历和if条件实现选择数据中的最大值。public ...
    99+
    2023-06-02
  • Android 通过SQLite数据库实现数据存储管理
    目录0 实验环境1 界面展示2 功能说明3 设计原理4 核心代码4.1 UI设计4.2 编写有关Java类(1)MainActivity类,用于初始化一些变量和注册组件:(2)DbH...
    99+
    2022-11-12
  • 通过mysql实现excel中的数据生成
    下面一起来了解下通过mysql实现excel中的数据生成,相信大家看完肯定会受益匪浅,文字在精不在多,希望通过mysql实现excel中的数据生成这篇短内容是你想要的。     ...
    99+
    2022-10-18
  • Java怎么通过注解实现接口输出时数据脱敏
    小编给大家分享一下Java怎么通过注解实现接口输出时数据脱敏,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Java注解实现接口输出数据脱敏在后台管理中,对于手机号...
    99+
    2023-06-22
  • Java 如何通过注解实现接口输出时数据脱敏
    目录Java注解实现接口输出数据脱敏先声明了一个注解我们目前只支持对手机号然后我们需要实现注解的拦截功能我对默认声明和脱敏名称和手机号进行了测试Java注解的字段脱敏处理定义需要脱敏...
    99+
    2022-11-12
  • java集成itextpdf实现通过pdf模板填充数据生成pdf
    文章目录 一、制作pdf模板1.1、使用excel制作一个表格1.2、转成pdf1.3、设置表单域1.4、最终模版效果 二、引入POM依赖三、代码实现3.1、工具类3.2、实体对象3.3、Controller 一、制作...
    99+
    2023-08-18
    java pdf
  • Java整合mybatis实现过滤数据
    目录场景例子执行流程配置切面方案实现场景 权限1:只能看到自己创建的数据权限2:只能看到本部门的数据权限3:查看全部数据 例子 小明有权限1: { "code": "200"...
    99+
    2023-01-14
    Java mybatis过滤数据 Java 过滤数据
  • Vue通过echarts实现数据图表化显示
    目录一、项目引入echarts二、创建容器三、配置图表一、项目引入echarts ecahrts官网 官网有许多图表案例,并且可以直接复制对应的配置代码。 vue项目中引入: 安装...
    99+
    2022-11-13
  • 怎么通过Python实现批量数据提取
    这篇文章主要介绍“怎么通过Python实现批量数据提取”,在日常操作中,相信很多人在怎么通过Python实现批量数据提取问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么通过Python实现批量数据提取”的疑...
    99+
    2023-07-05
  • 怎么通过Simulink实现数据滚动刷新
    本篇内容介绍了“怎么通过Simulink实现数据滚动刷新”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!对于...
    99+
    2022-10-19
  • 通过LogMiner实现Oracle数据库同步迁移
    目录通过LogMiner实现Oracle数据同步迁移一、实现过程1.创建目录2.配置LogMiner3.开启日志追加模式4.重启数据库5.创建数据同步用户6.创建数据字典7.加入需要...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作