并发编程:AQS(上)

一、AQS 是什么?

AQS 的全称为 AbstractQueuedSynchronizer ,翻译过来的意思就是抽象队列同步器。这个类在 java.util.concurrent.locks 包下面。

 AQS 就是一个抽象类,主要用来构建锁和同步器。

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
}

AQS 为构建锁和同步器提供了一些通用功能的实现,因此,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLockSemaphore,其他的诸如 ReentrantReadWriteLockSynchronousQueue等等皆是基于 AQS 的。

二、AQS 的原理是什么?

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁 实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten) 队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。在 CLH 同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。

CLH 队列结构如下图所示:

AQS(AbstractQueuedSynchronizer)的核心原理图如下:

 

AQS 使用 int 成员变量 state 表示同步状态,通过内置的 线程等待队列 来完成获取资源线程的排队工作。

state 变量由 volatile 修饰,用于展示当前临界资源的获锁情况。

// 共享变量,使用volatile修饰保证线程可见性
private volatile int state;

 另外,状态信息 state 可以通过 protected 类型的getState()setState()compareAndSetState() 进行操作。并且,这几个方法都是 final 修饰的,在子类中无法被重写。

//返回同步状态的当前值
protected final int getState() {
     return state;
}
 // 设置同步状态的值
protected final void setState(int newState) {
     state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
      return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

ReentrantLock 为例,state 初始值为 0,表示未锁定状态。A 线程 lock() 时,会调用 tryAcquire() 独占该锁并将 state+1 。此后,其他线程再 tryAcquire() 时就会失败,直到 A 线程 unlock()state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A 线程自己是可以重复获取此锁的(state 会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证 state 是能回到零态的。

再以 CountDownLatch 以例,任务分为 N 个子线程去执行,state 也初始化为 N(注意 N 要与线程个数一致)。这 N 个子线程是并行执行的,每个子线程执行完后countDown() 一次,state 会 CAS(Compare and Swap) 减 1。等到所有子线程都执行完后(即 state=0 ),会 unpark() 主调用线程,然后主调用线程就会从 await() 函数返回,继续后余动作。

三、Semaphore 有什么用?

synchronizedReentrantLock 都是一次只允许一个线程访问某个资源,而Semaphore(信号量)可以用来控制同时访问特定资源的线程数量。

Semaphore 的使用简单,我们这里假设有 N(N>5) 个线程来获取 Semaphore 中的共享资源,下面的代码表示同一时刻 N 个线程中只有 5 个线程能获取到共享资源,其他线程都会阻塞,只有获取到共享资源的线程才能执行。等到有线程释放了共享资源,其他阻塞的线程才能获取到。

// 初始共享资源数量
final Semaphore semaphore = new Semaphore(5);
// 获取1个许可
semaphore.acquire();
// 释放1个许可
semaphore. Release();

当初始的资源个数为 1 的时候,Semaphore 退化为排他锁。

Semaphore 有两种模式:。

  • 公平模式: 调用 acquire() 方法的顺序就是获取许可证的顺序,遵循 FIFO;
  • 非公平模式: 抢占式的。

Semaphore 对应的两个构造方法如下:

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。

Semaphore 通常用于那些资源有明确访问数量限制的场景比如限流(仅限于单机模式,实际项目中推荐使用 Redis +Lua 来做限流)。

四、Semaphore 的原理是什么?

Semaphore 是共享锁的一种实现,它默认构造 AQS 的 state 值为 permits,你可以将 permits 的值理解为许可证的数量,只有拿到许可证的线程才能执行。

调用semaphore.acquire() ,线程尝试获取许可证,如果 state >= 0 的话,则表示可以获取成功。如果获取成功的话,使用 CAS 操作去修改 state 的值 state=state-1。如果 state<0 的话,则表示许可证数量不足。此时会创建一个 Node 节点加入阻塞队列,挂起当前线程。

/**
 *  获取1个许可证
 */
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
/**
 * 共享模式下获取许可证,获取成功则返回,失败则加入阻塞队列,挂起线程
 */
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
      throw new InterruptedException();
        // 尝试获取许可证,arg为获取许可证个数,当可用许可证数减当前获取的许可证数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。
    if (tryAcquireShared(arg) < 0)
      doAcquireSharedInterruptibly(arg);
}

调用semaphore.release(); ,线程尝试释放许可证,并使用 CAS 操作去修改 state 的值 state=state+1。释放许可证成功之后,同时会唤醒同步队列中的一个线程。被唤醒的线程会重新尝试去修改 state 的值 state=state-1 ,如果 state>=0 则获取令牌成功,否则重新进入阻塞队列,挂起线程。

// 释放一个许可证
public void release() {
    sync.releaseShared(1);
}

// 释放共享锁,同时会唤醒同步队列中的一个线程。
public final boolean releaseShared(int arg) {
    //释放共享锁
    if (tryReleaseShared(arg)) {
      //唤醒同步队列中的一个线程
      doReleaseShared();
      return true;
    }
    return false;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

“健康中国 医路无忧——公益联盟”积极响应,国内首支公益陪诊师志愿队伍正式成立

在快节奏的现代生活中&#xff0c;就医不再是简单的“看病”那么简单。面对复杂的医疗流程、专业的医学术语、以及在陌生环境中的焦虑&#xff0c;患者及家属往往感到无所适从。此时&#xff0c;陪诊服务如同一束光&#xff0c;照亮了就医之路&#xff0c;它的重要性不仅体现在…

Visual Studio汇编代码高亮与自动补全

高亮插件&#xff1a;AsmDude (可以按照我的颜色进行设置&#xff0c;或者你自己改) 代码自动补全&#xff1a;CodeGeex (功能很多&#xff0c;支持的语言很多)&#xff0c;按Tab补全

骨传导耳机哪个品牌好用?精选五大高能骨传导耳机分享!

随着科技的进步和人们对健康生活的追求&#xff0c;骨传导耳机凭借其独特的设计理念和使用体验&#xff0c;迅速在运动爱好者和日常通勤族中流行起来。与传统耳机相比&#xff0c;骨传导耳机不仅能够提供清晰的音质&#xff0c;还能确保佩戴者在享受音乐的同时保持对外界环境的…

关于腾讯IM消息ID不统一的问题?服务端的MsgKey和前端的msgID不一样

角色>前端&#xff1a;web、小程序、客户端&#xff08;ios、安卓&#xff09;&#xff1b;服务端&#xff1b;腾讯IM&#xff1b; 1、背景 IM消息ID不一致&#xff0c;本地没有缓存历史数据&#xff0c;导致在调用腾讯sdk方法时&#xff0c;id不一致报错问题 2、调研目的…

HTML 揭秘:HTML 编码快速入门

HTML 揭秘&#xff1a;HTML 编码快速入门 一 . 前端知识介绍二 . HTML 介绍三 . HTML 快速入门四 . HTML 编辑器 - VSCode4.1 插件安装4.2 修改主题配色4.3 修改快捷键4.4 设置自动保存4.5 创建 HTML 文件4.5 书写 HTML 代码4.6 常见快捷键 五 . 基础标签5.1 字体标签5.1.1 col…

java实现图片爬取

开发环境 IntelliJ IDEA 2021.1.3 jdk1.8 Jsoup介绍 一款Java 的HTML解析器 jsoup 是一款Java 的HTML解析器&#xff0c;可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API&#xff0c;可通过DOM&#xff0c;CSS以及类似于jQuery的操作方法来取出和操作数…

SPI子系统

IO特性 SPI接口一般使用四条信号线通信&#xff1a; SDI&#xff08;数据输入&#xff09;&#xff0c;SDO&#xff08;数据输出&#xff09;&#xff0c;SCK&#xff08;时钟&#xff09;&#xff0c;CS&#xff08;片选&#xff09; MISO&#xff1a; 主设备输入/从设备输出…

服装|基于Java+vue的服装定制系统(源码+数据库+文档)

服装定制系统 目录 基于Javavue的服装定制系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布…

【PPT】文字突然变成方框

文章目录 前言一、问题描述二、解决方案参考文献 前言 在 ppt 画图时遇到的问题 一、问题描述 在 ppt 使用过程中&#xff0c;同一字体&#xff0c;有些变成了方框&#xff0c;有些没有变&#xff08;排除字体缺失问题&#xff09; 二、解决方案 如果是页数多了&#xff0…

MyEclipse2020安装教程(图文)

本章教程主要记录如何在Windows上安装MyEclipse2020.。 一、下载安装包 通过网盘分享的文件&#xff1a;Myeclipse 2020.rar 链接: https://pan.baidu.com/s/1fD2P0S0GU_zJlUHTPeXP-A?pwdv71m 提取码: v71m 二、安装步骤 1、打开解压后的文件夹&#xff0c;鼠标右击【myeclip…

Linux日志-日志小结

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux 系统中的日志是记录系统活动和事件的重要工具&#xff0c;它们可以帮助管理员监视系统状态、调查问题以及了解系统运行…

基于C#的UDP协议消息传输

1. 服务端 internal class Program{static void Main(string[] args){//1.创建SocketSocket socketServer new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);//2.绑定IP、端口号EndPoint endPoint new IPEndPoint(IPAddress.Parse("127.0.0.1&…

【Qt】实现一个小闹钟

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton>//按钮类 #include <QLineEdit>//行编辑器 #include <QLabel>//标签类 #include <QTextEdit>//输入框 #include <QTimerEvent>//定时器事件类 #inc…

StorageSync数据缓存API

uni.setStorageSyncs参数:将 data 存储在本地缓存中指定的 key 中&#xff0c;会覆盖掉原来该 key 对应的内容&#xff0c;这是一个同步接口。 uni.setStorageSync函数里面写两个参数,分别是key和值,两个参数名称可以随便取,如果有同名的key,那么后面key的值会覆盖掉前面key的值…

详解BMP图片格式以及关于Verilog图像处理的仿真环境搭建

文章目录 一、BMP位图简介二、BMP格式分析2.1 如何用十六进制打开BMP图片2.2 头文件格式2.3 信息头格式2.4 调色板格式2.5 位图数据格式2.6 位图对齐方式三、Verilog图像处理的仿真环境搭建3.1 Verilog文件操作指令3.1.1 条件编译 (`` `ifdef``、 `` `else``、`` `endif``)3.1.…

在ATECLOUD中如何修改原有电源自动测试系统中的测试方案?

对于大多数电源自动测试系统而言&#xff0c;已经完成定型的电源模块测试方案想要重新修改难度非常的大&#xff0c;除非电源生产企业将整个系统返厂回电源自动测试系统的生产商&#xff0c;否则很难自己调整修改内部的项目和方案&#xff0c;而电源自动测试系统的生产商对原有…

spring自动装配

spring自动装配 Spring 框架提供了一种机制&#xff0c;称为自动装配&#xff08;Autowired&#xff09;&#xff0c;它允许 Spring 容器自动将依赖注入到 Bean 中&#xff0c;而无需显式地使用 XML 配置文件或构造函数注入。自动装配简化了依赖注入的过程&#xff0c;使得代码…

element-ui打包之后图标不显示,woff、ttf加载404

1、bug 起因 昨天在 vue 项目中编写 element-ui 的树形结构的表格&#xff0c;发现项目中无法生效&#xff0c;定位问题之后发现项目使用的 element-ui 的版本是 2.4.11 。看了官方最新版本是 2.15.14&#xff0c;然后得知 2.4.11 版本是不支持表格树形结构的。于是决定升级 el…

云曦2024秋季开学考

ezezssrf 第一关&#xff1a;md5弱比较 yunxi%5B%5D1&wlgf%5B%5D2 第二关&#xff1a; md5强比较 需要在bp中传参&#xff0c;在hackbar里不行 yunxiiM%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DC V%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_B…

OFDM信号PARP的CCDF图

文章目录 引言代码代码疑难解答参考文献 引言 本书主要参考了文献1&#xff0c;但实际上该书中符号和表述的错误非常多&#xff08;只能说棒子是这样的&#xff09;&#xff1b;同时因为发表时间的关系&#xff0c;很多MATLAB代码进行了更新&#xff0c;原书提供的代码已经无法…