java并发之AQS详解(待更)

news2024/10/5 5:25:13

一、为什么要用AQS同步框架?

开发者如果不了解JMM和多线程编程,就会写出很多线程不安全的程序,即使是经验丰富的程序员,并发编程也难免会出错。

而对于java程序员来说,并发编程就变得容易得多了,因为并发编程大师Doug Lea为Java开发者提供了很多的并发容器和框架,而AQS就是java并发包下的一个核心框架。

AQS是抽象队列同步器,是用来构建Lock锁和同步组件的基础框架,JUC包下的很多锁和同步组件都是基于AQS构建的,比如ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore。

为什么说AQS是构建Lock锁和同步组件的基础框架呢?

这里补充一点Lock和synchronized关键字的区别的知识:

  1. synchronized关键字是JVM层面上实现的,而Lock是JUC包下的一个接口
  2. synchronized让获取锁的线程执行完同步代码之后释放锁,线程执行发生异常的情况下,jvm会让线程释放锁,使用Lock的话,需要程序员显式地在finally块中释放锁,不然会造成死锁
  3. synchronized会让一直没获取到锁的线程阻塞等待,而Lock没有获取锁可以选择不用一直等待
  4. synchronized在发生异常的时候会自动释放占有的锁,不会出现死锁,Lock在发生异常的时候,不会主动释放占用的锁,必须手动unlock来释放锁,可能会引起死锁的发生
  5. synchronized无法判断锁的状态,而Lock可以判断
  6. synchronized是可重入锁,不响应中断,非公平的,而Lock是可重入锁,可判断,可公平的
  7. 在性能上,如果是并发量小的话,synchronized效率高,并发量高的话Lock高。Lock还可以提高多个线程读操作的效率(可以通过ReadWriteLock实现读写分离),ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步

设想一下,如果要实现线程的同步和锁机制,需要考虑哪些问题?

  • 如何去获取锁和释放锁?
  • 如何处理同步状态?
  • 竞争失败时线程如何处理?

而上述问题,AQS已经帮你解决了:

  • AQS实现了对同步状态的原子性管理
  • AQS实现了对线程阻塞和解除阻塞的管理
  • AQS实现了对同步队列的管理

Lock锁如何使用AQS?
AQS是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。以ReentrantLock为例,它内部聚合了一个同步器Sync,这个同步器继承了AQS
在这里插入图片描述
如何使用AQS实现线程同步?
AQS定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器的主要使用方式是继承,子类通过继承AQS并实现它的抽象方法来管理同步状态,在抽象方法实现中免不了对同步状态进行更改,这时就需要使用同步器提供的3个方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))来进行操作,因为它们能够保证原子性的修改同步状态state字段。AQS既支持独占式地获取同步状态,也可以支持共享式地获取同步状态,这样就可以方便实现不同类型的同步组件。
在这里插入图片描述

二、什么是AQS同步框架?

AQS是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量state表示同步状态,通过内置的FIFO双向虚拟队列来完成资源获取线程的排队工作,并发包的作者Doug Lea期望AQS能够成为实现大部分同步需求的基础。

AQS使用的设计模式

AQS的设计是基于 模板方法模式 的,使用AQS需要继承它并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些模板方法将会调用使用者重写的方法。

同步器可重写的方法有哪些?

在这里插入图片描述
在重写AQS指定的方法时,需要使用AQS提供的如下3个方法来访问或修改同步状态:

  • getState():获取当前的同步状态
  • setState(int newState):设置当前同步状态
  • compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性(CAS机制实现)
AQS提供的模板方法有哪些?(部分)

在这里插入图片描述
AQS提供的模板方法基本分为3类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程的情况。

三、AQS的底层原理(源码分析)

笔者打算从使用者的角度来分析AQS的底层,因此下面用AQS去实现一个简单的独占锁,独占锁就是在同一时刻只能有一个线程获取到锁,其他没有获取到锁的线程进入同步队列中等待。

用AQS实现一个简单的独占锁Mutex(代码来自AQS源码注释)
package com.demo.LockTest;

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

public class Mutex implements Lock {
    //静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        //是否被占用
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        //当状态为0的时候获取锁
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //释放锁,将状态设置为0
        protected boolean tryRelease(int releases) {
            //释放锁操作必须拥有锁,否则抛出异常
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        //返回一个Condition,每个Condition都包含了一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    /**
     * 然后仅仅需要将同步操作代理到Sync上
     */
    private final Sync sync = new Sync();

    @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);
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

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

上面的独占锁Mutex是一个自定义的同步组件,Mutex中定义了一个静态内部类,该内部类继承了AQS并实现了独占式获取和释放同步状态。在tryAcquire(int acquire)方法中,使用CAS机制设置同步状态为1,并将当前线程标记为拥有锁的线程,否则进入同步队列中等待。tryRelease(int releases)方法中只是将同步状态设置为0,然后将拥有锁的线程设置为null。

在Mutex的实现中,可以发现,用户使用这个类的时候不会直接和内部同步器交互(被设为private),而是通过调用Mutex提供的方法来使用独占锁。而Mutex的方法实现仅仅是去调用了同步器中的模板方法而已,这样就大大降低了开发一个可靠自定义同步组件的门槛

AQS是怎么实现线程同步的?

从实现的角度分析,线程同步主要包括:

  • 同步队列
  • 独占式同步状态的获取与释放
  • 共享式同步状态的获取与释放
  • 超时获取同步状态等同步器的核心数据结构与模板方法
1)同步队列

AQS依赖内部的同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息封装成一个结点(Node),并将其加入同步队列,同时阻塞当前线程,当同步状态释放时,会把首结点唤醒,使其再次尝试获取同步状态。

同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点。
在这里插入图片描述

  • Node prev:前驱节点,当节点加入同步队列时被设置(尾部添加)
  • Node next:后继节点
  • Thread waiter:等待队列中的后继节点,若当前节点是共享的,那么这个字段将是一个SHARED常量,即节点类型(独占和共享)和等待队列中的后继节点共用同一个字段(?)。
  • int status:等待状态,包含CACELLED(1)、SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)、INITIAL(0),后续会详细介绍这些状态

节点是构成同步队列(或等待队列)的基础,AQS拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列的尾部,同步队列的基本结构如下所示:
在这里插入图片描述
注意看,AQS包含了两个节点类型的引用,一个指向head节点,一个指向tail节点。

AQS将节点加入到同步队列

在这里插入图片描述

首结点的设置

同步队列遵循FIFO(先进先出)的,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态的时候,会去唤醒后继节点,而后继节点在获取到同步状态时就会将自己设置为首结点,过程如下:
在这里插入图片描述
请注意,设置首节点是通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法不需要CAS来保证也是线程安全的,它只需要将首节点设置成为原首节点的后继节点并断开原首节点的next引用即可。

2)独占式同步状态的获取和释放

通过调用AQS的acquire(int arg)方法可以获取同步状态,这个方法不响应中断,即线程获取同步状态失败后进入同步队列中,后续对该线程进行中断操作的时候,这个线程也不会从同步队列中移除。

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

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

相关文章

《YOLOv7高阶自研》专栏介绍 CSDN独家改进创新实战 专栏目录

YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 🚀🚀🚀YOLOv7自研创新结合,轻松搞定科研 🍉🍉🍉持续更新中,定期更新不同数据集涨点情况 目录 全网首发&独家改进

C++DAY44

#include <iostream>using namespace std;class Animal//封装 动物 基类 { private:string name; public:Animal() {}Animal(string n):name(n){}virtual void perform() //虚函数{cout << "欢迎来到动物园" << endl;} };class Lion:public Animal…

LeetCode 1517. 查找拥有有效邮箱的用户

题目链接&#xff1a;1517. 查找拥有有效邮箱的用户 题目描述 表: Users Column NameTypeuser_idintnamevarcharmailvarchar user_id 是该表的主键&#xff08;具有唯一值的列&#xff09;。 该表包含了网站已注册用户的信息。有一些电子邮件是无效的。 编写一个解决方案&am…

LabVIEW将视觉生成器AI用作OPC服务器

LabVIEW将视觉生成器AI用作OPC服务器 介绍如何将视觉生成器AI配置为OPC服务器&#xff0c;并使用共享变量共享视觉生成器AI生成的结果。OPC是一系列标准规范&#xff0c;定义了来自不同制造商的控制设备之间的实时数据通信。OPC数据访问通信是基于客户端服务器的通信。 共享系…

适合自学的网络安全基础技能“蓝宝书”:《CTF那些事儿》

CTF比赛是快速提升网络安全实战技能的重要途径&#xff0c;已成为各个行业选拔网络安全人才的通用方法。但是&#xff0c;本书作者在从事CTF培训的过程中&#xff0c;发现存在几个突出的问题&#xff1a; 1&#xff09;线下CTF比赛培训中存在严重的 “最后一公里”问题 &#…

nodejs+vue+elementui养老院老年人服务系统er809

“养老智慧服务平台”是运用nodejs语言和vue框架&#xff0c;以MySQL数据库为基础而发出来的。为保证我国经济的持续性发展&#xff0c;必须要让互联网信息时代在我国日益壮大&#xff0c;蓬勃发展。伴随着信息社会的飞速发展&#xff0c;养老智慧服务平台所面临的问题也一个接…

Text-to-SQL小白入门(八)RLAIF论文:AI代替人类反馈的强化学习

学习RLAIF论文前&#xff0c;可以先学习一下基于人类反馈的强化学习RLHF&#xff0c;相关的微调方法&#xff08;比如强化学习系列RLHF、RRHF、RLTF、RRTF&#xff09;的论文、数据集、代码等汇总都可以参考GitHub项目&#xff1a;GitHub - eosphoros-ai/Awesome-Text2SQL: Cur…

【PX4】解决Resource not found: px4问题【踩坑实录】

【PX4】解决Resource not found: px4问题【踩坑实录】 文章目录 【PX4】解决Resource not found: px4问题【踩坑实录】1. 问题描述2. 错误排查 1. 问题描述 笔者在配置好px4的所有环境后&#xff0c;使用自己写的launch文件时&#xff0c;出现了报错 sjhsjhR9000X:~$ roslaunc…

【面试算法——动态规划 21】正则表达式匹配(hard) 交错字符串

10. 正则表达式匹配 链接: 10. 正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。 ‘.’ 匹配任意单个字符 ‘*’ 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xf…

2023.10.10 关于 线程安全 问题

目录 线程安全问题实例一 引发线程安全的原因 抢占式执行 多线程修改同一变量 操作的原子性 指令重排序 内存可见性问题 线程安全问题实例二 如何解决上述线程安全问题 volatile 关键字 Java 内存模型 JMM&#xff08;Java Memory Model&#xff09; 线程安全问题实…

解决echarts配置滚动(dataZoom)后导出图片数据不全问题

先展现一个echarts&#xff0c;并配置dataZoom&#xff0c;每页最多10条数据&#xff0c;超出滚动 <div class"echartsBox" id"echartsBox"></div>onMounted(() > {nextTick(() > {var chartDom document.getElementById(echartsBox);…

前端URL拼接路径参数

前端URL拼接路径参数 一、应用场景二、具体实现1.字符串拼接2.URL对象实现 四、完整代码 一、应用场景 我们有时候会遇到浏览器URL拼接参数的场景&#xff0c;例如页面跳转时&#xff0c;带上一个特定的标识&#xff1a;https://www.baidu.com?fromcsdn 二、具体实现 1.字符…

微软发布2023年10月补丁,修复了103个缺陷,包括2个活跃的漏洞利用

导语 最近&#xff0c;微软发布了2023年10月的补丁更新&#xff0c;共修复了103个缺陷。这些补丁包括两个正在被黑客利用的漏洞。让我们来看看这些补丁的具体内容和影响。 修复103个缺陷 微软此次的补丁更新共修复了103个缺陷&#xff0c;其中13个被评为严重&#xff0c;90个被…

gradle对应jdk版本

官网地址-兼容性矩阵&#xff1a;Compatibility Matrix Gradle运行在所有主要的操作系统上。它需要Java开发工具包&#xff08;JDK&#xff09;版本8或更高版本才能运行。有关详细信息&#xff0c;您可以查看兼容性矩阵。

DailyPractice.2023.10.12

文章目录 1.[1. 两数之和]2.[49. 字母异位词分组]3.[128. 最长连续序列]4.[283. 移动零]5.[11. 盛最多水的容器]6.[15. 三数之和]7.[3. 无重复字符的最长子串]8.[206. 反转链表]9.[141. 环形链表]10.[160. 相交链表] 1.[1. 两数之和] 1. 两数之和 class Solution { public:ve…

单目标优化算法:火鹰优化算法(Fire Hawk Optimizer,FHO)求解23个函数--提供MATLAB代码

一、火鹰优化算法FHO 火鹰优化算法&#xff08;Fire Hawk Optimizer&#xff0c;FHO&#xff09;由Mahdi Azizi等人于2022年提出&#xff0c;该算法性能高效&#xff0c;思路新颖。 单目标优化&#xff1a;火鹰优化算法&#xff08;Fire Hawk Optimizer&#xff0c;FHO&#…

使用Tortoisegit界面拉起master主分支以副分支以及命令行操作

文章目录 1、Gui操作界面2、命令行操作 1、Gui操作界面 "小乌龟"通常指的是Git的图形用户界面&#xff08;GUI&#xff09;工具&#xff0c;其中比较常见的是TortoiseGit。下面是使用TortoiseGit来拉取&#xff08;checkout&#xff09;一个Git分支的步骤&#xff1a…

数字时代的自我呈现:探索个人形象打造的创新工具——FaceChain深度学习模型工具

数字时代的自我呈现&#xff1a;探索个人形象打造的创新工具——FaceChain深度学习模型工具 1.介绍 FaceChain是一个可以用来打造个人数字形象的深度学习模型工具。用户仅需要提供最低一张照片即可获得独属于自己的个人形象数字替身。FaceChain支持在gradio的界面中使用模型训…

机器学习(22)---信息熵、纯度、条件熵、信息增益

文章目录 1、信息熵2、信息增益3、例题分析 1、信息熵 1. 信息熵(information entropy)是度量样本集合纯度最常用的一种指标。信息的混乱程度越大&#xff0c;不确定性越大&#xff0c;信息熵越大&#xff1b;对于纯度&#xff0c;就是信息熵越大&#xff0c;纯度越低。 2. 纯度…

CSI2与CDPHY学习

注意&#xff1a;本文是基于CSI2-V3.0 spec。 其中CPHY为 V2.0 DPHY为V2.5 本文主要在packet级别介绍CSI2与对应的CDPHY&#xff0c;需要注意的是CDPHY的burst数据就是以packet为单位 1.CSI-CPHY 1.1CPHY的多lane分配与合并 csi-cphy规定至少需要一条lane用于传输视频&am…