Java - AQS(一)

news2024/11/26 18:38:49

Java - AQS(一)

在Java中,AQS代表AbstractQueuedSynchronizer(抽象队列同步器)。AQS是Java并发包中用于构建同步器的基础框架。它提供了一种实现同步状态管理、线程等待和通知的机制。

AQS主要通过一个int类型的状态值来表示同步状态,并提供了一些方法来操作和管理这个状态。它使用一个FIFO(先进先出)的等待队列来管理等待线程,并通过内部的一些算法和机制来确保线程的安全等待和唤醒。

AQS的核心思想是通过继承和重写来实现具体的同步器。它提供了几个关键的方法供子类实现,包括getState()(获取当前状态值)、setState()(设置状态值)、acquire()(获取同步状态)、release()(释放同步状态)等等。子类可以根据自己的需求实现这些方法来创建不同类型的同步器,例如ReentrantLock、Semaphore等。

在这里插入图片描述

java.util.concurrent.locks包下,有两个这样的类:

  • AbstractQueuedSynchronizer
  • AbstractQueuedLongSynchronizer

这两个类的唯一区别就是:

  • AbstractQueuedSynchronizer内部维护的state变量是int类型
  • AbstractQueuedLongSynchronizer内部维护的state变量是long类型

我们常说的AQS其实泛指的就是这两个类,即抽象队列同步器

AQS其实主要做了这么几件事情:

  • 同步状态(state)的维护管理
  • 等待队列的维护管理
  • 线程的阻塞与唤醒

可以通过类比来理解AQS的原理。

想象一下,你在一家餐厅等待就餐。餐厅只有一张餐桌,同时只能容纳一组客人就座。当你到达餐厅时,你会查看餐桌的状态,如果餐桌已经被占用,你就需要在等待区等待。一旦餐桌被释放,下一位等待的客人将获得就餐权,并将其状态设置为“占用”。

这里,餐桌就可以类比为AQS中的同步状态,每个客人就是一个线程。餐厅的等待区则对应AQS中的等待队列。通过这种方式,AQS提供了一种线程同步的机制。

AQS内部维护了一个状态变量,可以通过getState()方法获取该状态。线程可以通过acquire()方法来获取同步状态,如果当前状态已被其他线程占用,那么该线程将被放入AQS的等待队列中,进入等待状态。当其他线程释放同步状态时,AQS会从等待队列中选择一个线程,将状态授予它,使其继续执行。这个过程就类似于等待餐桌的释放和下一位客人就座。

AQS还提供了一些方法供子类实现,如tryAcquire()tryRelease()等。子类可以通过重写这些方法来定义特定的获取和释放同步状态的逻辑,以实现不同类型的同步器。

总之,AQS提供了一个通用的框架,通过维护同步状态和等待队列来实现线程的同步和互斥。类比于餐厅中的餐桌和等待区,可以帮助理解AQS的基本原理。

内部类

Node

在AbstractQueuedSynchronizer(AQS)类中,Node类是用于构建等待队列的节点。每个线程在等待获取同步状态时,都会被封装成一个Node节点并加入到AQS的等待队列中。 看过LinkedList源码,可以发现这里类似于内部类中的Node,都有头尾指针,只不过加入了私有变量waitStatus状态,同时用 volatile Thread存放内存可见性线程

/**
* 源码翻译
*
*等待队列节点类。
等待队列是“CLH”(Craig、Landin和Hagersten)锁定队列的一种变体。CLH锁通常用于自旋锁。相反,我们将它们用于阻塞同步器,但使用相同的基本策略,即在其节点的前身中保存有关线程的一些控制信息。每个节点中的“状态”字段跟踪线程是否应该阻塞。当一个节点的前身释放时,它会收到信号。队列的每个节点充当特定通知样式的监视器,持有单个等待线程。状态字段并不控制线程是否被授予锁等。如果线程位于队列的第一个,则可以尝试获取。但做第一并不能保证成功;它只赋予人们争辩的权利。因此,当前释放的竞争者线程可能需要重新等待。
要排队进入CLH锁,需要自动地将其拼接为新尾。要脱队,只需设置head字段。
+------+ prev +-----+ +-----+
头部| | <---- | | <---- |尾部
+------+ +-----+ +-----+

插入到CLH队列只需要对“尾部”进行单个原子操作,因此存在从未排队到排队的简单原子分界点。类似地,脱离队列只涉及更新“头部”。然而,节点需要做更多的工作来确定谁是它们的后继者,部分原因是为了处理由于超时和中断而可能取消的情况。
“prev”链接(在原来的CLH锁中没有使用)主要用于处理取消。如果一个节点被取消,它的后继节点(通常)被重新链接到一个未取消的前继节点。有关自旋锁的类似力学解释,请参阅Scott和Scherer在http://www.cs.rochester.edu/u/scott/synchronization/上发表的论文
我们还使用“next”链接来执行阻塞机制。每个节点的线程id都保存在自己的节点中,因此前导节点通过遍历下一个链接来确定它是哪个线程,从而通知要唤醒的下一个节点。后继者的确定必须避免与新排队的节点竞争,以设置其前继者的“next”字段。当节点的后继节点为空时,可以通过从自动更新的“尾部”向后检查来解决这个问题。(或者换句话说,下一个链接是一种优化,因此我们通常不需要向后扫描。)
消去为基本算法引入了一定的保守性。由于我们必须轮询其他节点的取消,因此我们可能无法注意到被取消的节点是在我们前面还是后面。解决这个问题的办法是,在继任者被取消时,总是取消他们的停车位,让他们在一个新的前任上稳定下来,除非我们能找到一个未被取消的前任来承担这项责任。
CLH队列需要一个虚拟头节点才能启动。但我们不会在构建时创建它们,因为如果没有争用,那将是浪费精力。相反,将构造节点,并在第一次争用时设置头和尾指针。
等待条件的线程使用相同的节点,但使用额外的链接。条件只需要在简单(非并发)链接队列中链接节点,因为它们只有在独占状态下才会被访问。等待时,将节点插入条件队列。收到信号后,节点被转移到主队列。状态字段的特殊值用于标记节点所在的队列。
/**
static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        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;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        Node nextWaiter;

     
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

  
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    
        }

        Node(Thread thread, Node mode) {     
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) {  
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

Node类包含了多个字段,其中比较重要的字段有:

  1. prev:指向前一个节点的引用,形成了一个双向链表结构,用于在等待队列中维护节点的顺序。
  2. next:指向后一个节点的引用,同样用于维护节点的顺序。
  3. thread:表示持有该节点的线程。
  4. waitStatus:表示节点的等待状态,具体的取值有以下几种:
    • CANCELLED:表示节点已被取消。
    • SIGNAL:表示节点需要唤醒后继节点。
    • CONDITION:表示节点处于等待队列中,等待在条件变量上。
    • PROPAGATE:表示释放共享锁时需要进行传播。
    • 0:表示节点处于初始状态。
  5. nextWaiter:指向下一个等待在同一个条件变量上的节点。

通过使用Node类,AQS可以构建一个基于双向链表的等待队列,用于管理等待获取同步状态的线程。等待队列中的每个节点都代表一个等待线程,并包含了线程的相关信息和状态。AQS可以通过操作等待队列中的节点来实现线程的等待和唤醒机制,以及实现不同类型的同步器的特定语义。

Node类在AQS内部使用,并不需要直接在使用AQS的代码中引用或操作该类。它是AQS内部数据结构的一部分,用于支持等待队列的构建和管理。

ConditionObject

官方文档:

作为Lock实现基础的AbstractQueuedSynchronizer的条件实现。
该类的方法文档从锁定和条件用户的角度描述了机制,而不是行为规范。这个类的导出版本通常需要附带描述依赖于相关AbstractQueuedSynchronizer的条件语义的文档。
public class ConditionObject implements Condition, java.io.Serializable {
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
}

ConditionObject类(状态对象)是AbstractQueuedSynchronizer(AQS)内部的一个内部类,看起来有点抽象,简而言之,它实现了Condition接口,用于支持基于条件的线程等待和唤醒机制。

在多线程编程中,有时需要线程在某个条件满足时等待,而在条件不满足时进行阻塞,直到其他线程发出相应的信号。ConditionObject提供了这样的功能,它可以和AQS一起使用,使得同步器能够支持条件等待和唤醒操作。

ConditionObject提供了几个关键的方法,包括:

  1. await():使当前线程等待,并释放持有的同步状态,直到被其他线程发出的信号唤醒。
  2. awaitUninterruptibly():与await()类似,但是在等待过程中不响应中断信号。
  3. signal():唤醒等待在该条件上的一个线程,使其继续执行。
  4. signalAll():唤醒等待在该条件上的所有线程,使它们继续执行。

小结

上方有Node类下方ConditionObject中又存有firstWaiter对象,他们之间有什么关联?

在AQS的等待队列中,每个节点都是一个Node对象,用于表示一个等待条件的线程。这些节点通过prevnext字段组成双向链表结构,形成了等待队列。

当使用Condition对象进行等待操作时,会将当前线程封装成一个Node节点,并将其加入到等待队列的尾部,通过lastWaiter字段来指向新添加的节点。当条件满足并需要唤醒等待线程时,可以从等待队列的头部获取第一个等待线程的Node节点,通过firstWaiter字段来引用它。

通过firstWaiterlastWaiter字段,ConditionObject可以有效地维护和操作等待队列,实现条件等待和唤醒的功能。这与AQS的等待队列和Node节点密切相关,用于支持基于条件的线程等待和唤醒机制。

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

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

相关文章

轻松玩转开源大语言模型bloom(四)

前言 前几篇都围绕着语言模型的decoding strategy来讲述&#xff0c;今天将进入进阶篇&#xff0c;在解码策略效果有限和提示词修改也无法满意的情况下如何提升模型的效果呢&#xff1f;这时我们需要对大语言模型进行fine-tune&#xff0c;即微调。一般我们用的大语言模型都是…

chatgpt赋能Python-python5个数从小到大排序

Python中的5个数从小到大排序 在Python中&#xff0c;排序是一个常见的操作。我们经常需要对一组数据进行排序&#xff0c;以便更方便地对数据进行分析和处理。在本文中&#xff0c;我们将探讨Python中如何排序5个数&#xff0c;具体来说&#xff0c;是从小到大排序。 介绍 …

chatgpt赋能Python-python5__3

Python5%-3: 介绍和结论 什么是Python5%-3 Python5%-3是在Python 3语言版本中增加的一个新特性&#xff0c;它是Python语言中对移动开发的支持扩展&#xff0c;这个特性被称为Python5%-3。 具体来说&#xff0c;Python5%-3允许开发者能够更方便地创建移动应用程序&#xff0…

大脑MRI去噪技术研究进展

导读 磁共振(MR)图像诊断的准确性取决于图像的质量&#xff0c;而图像质量下降的主要原因是由于噪声和伪影。噪声是由成像环境错误或传输系统失真所引起的。因此&#xff0c;去噪方法对提高图像质量起着重要作用。然而&#xff0c;在去噪和保留结构细节之间需要权衡。现有的大…

【Linux】Linux 下的权限(初)

d1 目录下有目录dir&#xff0c;和一个普通文件 test.c 重点看到文件的各种权限&#xff0c;拆分清晰地理解&#xff08;重&#xff09; 观察到权限和文件类型一坨除了第一列的文件类型其他的都是文件相关的权限&#xff0c;而且是三个三个分开 为什么分开呢&#xff1f;是因为…

【leetcode】456. 132 模式 单调栈出栈特性

看题意是要在数组中找到一个大于左右元素波峰。 一开始看数据量是 10e5&#xff0c;还以为是 nlogn算法。没想到居然是个 n 的单调栈。 这道题利用了递减单调栈出栈的特性, 出栈元素 k、栈中某一个特定元素 j 满足 nums[j] > nums[k]&#xff0c;如果从数组后面向前遍历的…

什么是uni-app?为什么要学习uni-app?

文章目录 前言 一、什么是uni-app框架&#xff1f; 二、为什么要学&#xff1f; 三、uni-app开发的适用场景是什么&#xff1f; 总结 前言 随着前端技术的不断发展进步&#xff0c;跨端开发成为了程序猿不得不面临的一个难题&#xff0c;uni-app的出现解决了程序猿不断重复…

《Java 核心技术面试》课程笔记(十)

如何保证集合是线程安全的? 典型回答 Java 提供了不同层⾯的线程安全支持。 在传统集合框架内部&#xff0c;除了 Hashtable 等同步容器&#xff0c;还提供了所谓的同步包装器&#xff08;Synchronized Wrapper&#xff09;&#xff0c;我们可以调用 Collections 工具类提供…

16款ChatGPT工具,太炸裂了,收藏!

1.ChatGPT for google 一个浏览器插件&#xff0c;可搭配现有的搜索引擎来使用。 最大化搜索效率&#xff0c;对搜索体验的提升相当离谱&#xff1a; 安装完插件后&#xff0c;在搜索引擎搜索任何问题&#xff0c;都能获取两份答案。 左边是谷歌抓取的全网资源&#xff0c;右…

完美解决:“因为计算机丢失VCRUNTIME140_1.dll”

VCRUNTIME140_1.dll是Microsoft Visual C Redistributable的一个组件&#xff0c;它包含了许多用于C编程的函数和类。如果你的系统缺少了这个文件&#xff0c;那么你可能会遇到“找不到VCRUNTIME140_1.dll无法继续执行代码”的错误提示。 方法1&#xff1a;直接复制VCRUNTIME…

python多进程与多线程

1 Python多线程 1.1 GIL 其他语言&#xff0c;CPU是多核时是支持多个线程同时执行。但在Python中&#xff0c;无论是单核还是多核&#xff0c;同时只能由一个线程在执行。其根源是GIL的存在。GIL的全称是Global Interpreter Lock(全局解释器锁)&#xff0c;来源是Python设计之…

[MySQL]事务的浅谈

欲买桂花同载酒 终不似 少年游 目录 1.MySQL为什么需要事务 2.MySQL对事务的支持 3.关于事务的操作 控制方式(对于单条SQL) 控制方式二(START TRANSACTION 或 BEGIN) 4.关于ACID 5.关于事务的隔离级别 5.1事务可能存在的问题 5.2事务隔离级别 5.3难点(RR 级别 …

Autosar RTE S/R接口implicit与Explicit的实现与区别

文章目录 前言接口的代码implicitIReadIWrite ExplicitReadWrite 区别与使用场景总结 前言 Autosar官方文档阅读起来比较费劲&#xff0c;一般从实际应用中来了解更多规范中的内容。本文介绍最常用的RTE S/R接口的implicit隐式与Explicit显式两种方式的实现与差别 接口的代码…

SSM + MySQL + Vue2.x + ElementU 图书管理系统(期末作业)

图书管理系统 项目介绍 &#x1f525; SSM MySQL Vue2.x ElementUI &#x1f525; 本项目使用 Idea 开发工具采用当前最火的Java Web前端框架开发&#xff0c;在保证质量的同时界面美观&#xff0c;交互友好&#xff0c;实在是期末大作业的首选项目。 软件架构 使用软…

HashMap源码详解

文章目录 简单介绍提出问题流程说明及验证put元素的流程怎样找到要存储的下标位置的?什么时候会扩容? 加载因子、阈值这些有什么含义?怎样扩容的?扩容的流程.链表可以转成红黑树, 那会从红黑树转成链表吗?什么时候会从链表转成红黑树 小总结 简单介绍 HashMap是Java中最最…

Metal入门学习:绘制纹理图片

一、编程指南PDF下载链接(中英文档&#xff09; 1、Metal编程指南PDF链接 https://github.com/dennie-lee/ios_tech_record/raw/main/Metal学习PDF/Metal 编程指南.pdf 2、Metal着色语言(Metal Shader Language:简称MSL)编程指南PDF链接 https://github.com/dennie-lee/ios_te…

Linux服务器丨重测序数据分析常用软件安装指南

重测序分析软件安装指南 重测序&#xff08;resequencing&#xff09;是指对已知基因组进行高通量测序&#xff0c;以检测个体或种群的遗传变异&#xff0c;从而研究基因组的结构和功能。与全基因组测序不同&#xff0c;重测序通常只对一部分基因组进行测序&#xff0c;例如外显…

【2023 雷泽杯 · Misc】我是签到题

一道图片隐写题 目录 一、题目 二、思路 1.010editor查看源码 2.检索头部关键字段 3.图片隐写——高度隐写 一、题目 看不到这个图片对吧&#xff0c;这就是题目原本的样子。 二、思路 1.010editor查看源码 很明显的rar特征&#xff0c;尝试将后缀改成rar后打开。 发…

《Java就业班体系结构.pdf》:从入门到精通,掌握Java开发的终极指南,成为熟练高级开发者!

Java开发的终极指南 第1阶段&#xff1a;JAVA开篇第2阶段&#xff1a;JAVA语言语法第3阶段&#xff1a;集成开发工具的使用第4阶段&#xff1a;面向对象第5阶段&#xff1a;JavaSE进阶学习第6阶段&#xff1a;数据库JDBC第7阶段&#xff1a;前端精讲第8阶段&#xff1a;JavaEE第…

RocketMQ的学习历程(4)----消息处理 (2)

1.消费者的两种消费模式 顺序消费模式&#xff08;Sequential Consumer Mode&#xff09;&#xff1a; 在顺序消费模式下&#xff0c;消息队列中的消息按照发送的顺序被消费者顺序消费。每个消息队列只会被一个消费者线程消费&#xff0c;确保消息的顺序性。这种模式适用于需要…