Java - AbstractQueuedSynchronizer

AQS简介

AQS全称AbstractQueuedSynchronizer,抽象队列同步器,是一个实现同步组件的基础框架。AQS使用一个int类型的成员变量state维护同步状态,通过内置的同步队列(CLH锁、FIFO)完成线程的排队工作,底层主要是通过CAS操作和volatile特性实现。

它主要包含两种模式:独占模式(例如ReentrantLock)和共享模式(例如CountDownLatch)。关键方法包括acquire和release,用于独占模式下的获取和释放操作,以及acquireShared和releaseShared,用于共享模式下的操作。

它简化了同步组件的实现方式,屏蔽了同步状态的管理、线程的排队等待与唤醒等底层操作。我们只需要通过继承实现AQS中的抽象方法,即可实现不同同步语义的同步组件。例如ReentrantLock这种独占式同步组件,核心逻辑仅重写了tryAcquire和tryRelease等少数方法,就实现了可重入独占式锁的功能。

AQS通过继承来扩展其功能,子类通过继承AQS实现抽象方法来管理同步状态,同步组件则通过聚合子类的方法来实现自己的同步特性,独占式或共享式。

使用AQS实现一个自定义排他锁

继承AQS:
创建一个新的类,继承AbstractQueuedSynchronizer。

实现核心方法:
实现tryAcquire和tryRelease方法(用于独占模式),或实现tryAcquireShared和tryReleaseShared方法(用于共享模式)。

内部类Sync:
通常创建一个内部静态类Sync,扩展AQS并实现上述方法。

对外方法:
在外部类中,提供获取和释放的方法,内部调用AQS的方法acquire、release、acquireShared和releaseShared。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;public class SimpleLock {private final Sync sync = new Sync();private static class Sync extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}@Overrideprotected boolean tryRelease(int arg) {if (getState() == 0) throw new IllegalMonitorStateException();setExclusiveOwnerThread(null);setState(0);return true;}@Overrideprotected boolean isHeldExclusively() {return getExclusiveOwnerThread() == Thread.currentThread();}}public void lock() {sync.acquire(1);}public void unlock() {sync.release(1);}public boolean isLocked() {return sync.isHeldExclusively();}
}

使用AQS实现一个自定义共享锁

import java.util.concurrent.locks.AbstractQueuedSynchronizer;public class SimpleSemaphore {private final Sync sync;public SimpleSemaphore(int permits) {sync = new Sync(permits);}private static class Sync extends AbstractQueuedSynchronizer {Sync(int permits) {setState(permits);}@Overrideprotected int tryAcquireShared(int acquires) {for (;;) {int available = getState();int remaining = available - acquires;if (remaining < 0 || compareAndSetState(available, remaining)) {return remaining;}}}@Overrideprotected boolean tryReleaseShared(int releases) {for (;;) {int current = getState();int next = current + releases;if (compareAndSetState(current, next)) {return true;}}}}public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}public void release() {sync.releaseShared(1);}
}

同步队列和条件队列的区别


同步队列(synchronization queue)和条件队列(condition queue)是AbstractQueuedSynchronizer(AQS)中的两个不同的队列,它们有不同的用途和工作机制。

同步队列

  • 用途:管理所有等待获取锁或同步状态的线程。
  • 触发条件:当线程尝试获取锁或同步状态失败时进入队列。
  • 结构:双向链表。
  • 作用:维护线程的顺序,确保公平竞争。
  • 唤醒机制:当锁释放或同步状态改变时,唤醒队列中的下一个线程。

条件队列

  • 用途:管理等待特定条件的线程(通过ConditionObject)。
  • 触发条件:当线程调用await()方法时进入队列,并释放当前持有的锁。
  • 结构:单向链表。
  • 作用:等待特定条件的满足,如某个状态的变化。
  • 唤醒机制:当条件满足时(通过signal()或signalAll()),线程从条件队列移动到同步队列,等待重新获取锁。

为什么await方法一定要加锁才能调用

我理解条件队列现在的实现是在await方法先完全释放锁,再进入休眠状态等待唤醒,被唤醒后重新尝试加锁。先完全释放锁,是为了防止当前线程持有锁时错误休眠,若当前线程持有锁进入休眠状态,即使当前线程被唤醒,也是尝试加锁而不是解锁,那么其他线程永远无法拿到锁。因此先完全解锁再休眠,被唤醒后重新尝试加锁,应该是出于防止锁饥饿与死锁的考虑。

AQS独占式加锁(以ReentrantLock加锁为例)

加锁流程

加锁源码分析

解锁流程

解锁源码分析

CLH锁(同步队列的原型)

CLH锁是对自旋锁的一种改良,自旋锁只适合竞争不激烈,加锁时间短的场景,原因是自旋锁有2个问题,第一,存在锁饥饿问题,可能会出现某个线程一直拿不到锁的情况;第二,锁状态中心化,激烈竞争时会导致CPU的高速缓存频繁失效,导致性能降低。

CLH队列锁有效地解决了以上2个问题,首先,CLH锁通过增加队列进行排队,确保所有线程都有机会拿到锁;第二,CLH锁的每个线程监视的都是上一个节点的锁状态,因此锁状态是去中心化的,不会导致CPU高速缓存频繁失效。

CLH锁实现源码

import java.util.concurrent.atomic.AtomicReference;public class CLH {private final ThreadLocal<Node> node = ThreadLocal.withInitial(Node::new);private final AtomicReference<Node> tail = new AtomicReference<>(new Node());private static class Node {// ①锁定状态必须用volatile修饰以确保内存可见性private volatile boolean locked;}public void lock() {Node node = this.node.get();node.locked = true;Node pre = this.tail.getAndSet(node);while (pre.locked) ;}public void unlock() {Node node = this.node.get();node.locked = false;// ②重置当前线程对应的Node节点避免死锁this.node.set(new Node());}
}

原理分析

观察代码可以发现,CLH没有前驱或后继指针,因为CLH锁是一种隐式队列,入队只需要添加新的tail节点,出队只需要修改头部状态。注释①处必须用volatile修饰,以确保内存可见性与代码有序性。注释②处必须重置线程Node,否则当一个线程重复加锁时,就有可能死锁,死锁原因可以看这篇文章的解锁分析部分。

CLH锁的优点是性能优异、实现简单、加锁公平、扩展性强。但缺点是有自旋操作,长时间加锁会浪费CPU性能,再就是功能单一,不支持复杂的功能。

针对以上两个缺点,AQS都做了改进。针对第一个缺点,将自旋改为了线程阻塞等待;针对第二个缺点,AQS做了很多改造和扩展,例如增加了每个节点的状态waitStatus、显式维护了前驱和后继节点等等,基于这些扩展,AQS实现了独占锁、共享锁、线程排队、状态传播等复杂功能。

参考链接

从ReentrantLock的实现看AQS的原理及应用 - 美团技术团队

AQS 详解 | JavaGuide

AQS的前菜—详解CLH队列锁_clh锁-CSDN博客

Java AQS 核心数据结构-CLH 锁

JUC锁:核心类AQS源码详解 - 拿了桔子跑-范德依彪 - 博客园

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

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

相关文章

echarts 散点图修改散点图中图形形状颜色大小

话不多说&#xff0c;直接上代码 let option {color:[xxx, xxx, xxx, xxx], //直接设置color可修改图形颜色title: {text: 散点图图形,},tooltip: {trigger: axis,axisPointer: {type: cross}},legend: {top: 2,right:2,itemWidth: 10,itemHeight: 10,textStyle:{fontSize:14}…

shell脚本条件语句和循环语句

文章目录 一、条件语句测试比较整数数值字符串比较逻辑运算双中括号&#xff08; &#xff09;{ }if语句结构case语句 二、循环语句基础知识for循环whileuntil双重循环及跳出循环 一、条件语句 测试 条件测试&#xff1a;判断某需求是否满足&#xff0c;需要由测试机制来实现…

视频分类——C3D使用

整体比较分散&#xff0c;可能很多源码都需要修改&#xff0c;需要有耐心。 一、数据准备 PS 调研后&#xff0c;上手容易代码比较简洁的是&#xff1a;https://github.com/Niki173/C3D/tree/main 因为源码很多参数都写死到了源码中&#xff0c;没有解耦&#xff0c;并且默…

5.20Git

版本控制工具Git&#xff0c;其他的工具还有SVN 共享代码&#xff0c;追溯记录&#xff0c;存储.c文件 Git实现的功能&#xff1a;回溯&#xff08;以前某个时间节点的数据情况&#xff09;共享&#xff08;大家共享修改&#xff09; Git&#xff1a;80% SVN&#xff…

The Missing Semester of Your CS Education(计算机教育中缺失的一课)

Shell 工具和脚本(Shell Tools and Scripting) 一、shell脚本 1.1、变量赋值 在bash中为变量赋值的语法是foobar&#xff0c;访问变量中存储的数值&#xff0c;其语法为 $foo。 需要注意的是&#xff0c;foo bar &#xff08;使用空格隔开&#xff09;是不能正确工作的&…

基于灰狼优化算法优化RBF(GWO-RBF)的数据回归预测(多输入多输出)

代码原理及流程 基于灰狼优化算法优化多输入多输出&#xff08;MIMO&#xff09;的RBF神经网络的数据回归预测&#xff0c;可以采取以下步骤&#xff1a; 1. 数据准备&#xff1a;准备包含多个输入特征和多个输出目标的数据集&#xff0c;确保数据已经经过预处理和归一化。 …

CSS基础(第二天)

Emmet语法 快速生成HTML结构语法 1. 生成标签 直接输入标签名 按tab键即可 比如 div 然后tab 键&#xff0c; 就可以生成 <div></div> 2. 如果想要生成多个相同标签 加上 * 就可以了 比如 div*3 就可以快速生成3个div 3. 如果有父子级关系的标签&#xff0c;可以…

算法刷题笔记 数的范围(C++实现)(二分法重要例题)

文章目录 题目描述题目思路题目代码&#xff08;C&#xff09;题目感想 题目描述 给定一个按照升序排列的长度为n的整数数组&#xff0c;以及q个查询。对于每个查询&#xff0c;返回一个元素k的起始位置和终止位置&#xff08;位置从0开始计数&#xff09;。如果数组中不存在该…

自动化测试--利用pytest实现整条业务链路测试

​ 概述 前面一章讲解了单个接口的测试&#xff0c;但是实际项目中&#xff0c;因为权限和登录状态的限制&#xff0c;大部分接口没办法直接访问到&#xff0c;这时候我们想访问到一个系统的接口&#xff0c;就需要模拟用户登录拿到用户的token和所拥有的权限之后再将这些信息…

vivado2020.2创建hls仿真工程实现led闪烁

下载vivado2020.2后会有这个出现在桌面 点击进入创建工程&#xff0c;这里注意不要有前面的\我再复制的时候复制错了导致创建失败 按f光标就会跳转到下一个f开头的函数处&#xff0c;要查找其他函数也同理 生成了一个synthesis summary文件 找到目录下生成的.v文件 an 点…

Pod进阶——资源限制以及探针检查

目录 一、资源限制 1、资源限制定义&#xff1a; 2、资源限制request和limit资源约束 3、Pod和容器的资源请求和限制 4、官方文档示例 5、CPU资源单位 6、内存资源单位 7、资源限制实例 ①编写yaml资源配置清单 ②释放内存&#xff08;node节点&#xff0c;以node01为…

有些错误,常犯常新、常新常犯:记录一个使用element-plus的tooltip组件的错误

使用element-plus的tooltip组件&#xff0c;最开始的写法是这样的&#xff1a; <el-tooltipclass"box-item"effect"dark"content"tooltip content" ><el-button v-if"isDisabled" :underline"false" type"pr…

【C语言】程序员自我修养之文件操作

【C语言】程序员自我修养之文件操作 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C语言学习之路 文章目录 【C语言】程序员自我修养之文件操作前言一.文件介绍1.1为什么使用文件1.2文件分类1.3二进制文件和文本文件 二.文件的打开和关闭2.…

桌面藏线大法

1有线改无线&#xff1a; 蓝牙鼠标 蓝牙键盘 蓝牙耳机 2将排插贴到桌子底下 购物软件上搜 3断舍离 不要的电子产品统统扔掉 4 洞洞板和挂钩 这个不用介绍了

暴雨信息液冷计算解决方案亮相CCIG 2024

5月24日&#xff0c;2024中国图象图形大会&#xff08;CCIG&#xff09;在陕西西安正式开幕。作为涵盖图像图形各专业领域的综合性的全国性学术会议&#xff0c;CCIG面向开放创新、交叉融合的发展趋势&#xff0c;为图像图形相关领域的专家学者和产业界的同仁&#xff0c;搭建了…

Java+Spring+ MySQL + MyCat云HIS有哪些优势?智慧医疗云(HIS)低成本与安全保障的完美结合

JavaSpring MySQL MyCat云HIS有哪些优势&#xff1f;智慧医疗云(HIS)低成本与安全保障的完美结合 云HIS的优点包括节省成本、便捷高效、稳妥安全等。通过云HIS&#xff0c;医疗机构无需在本地建立机房、购买服务器和应用软件&#xff0c;降低了硬件和人力成本。同时&#xff0…

虚拟化介绍

虚拟化介绍 概述概念特点优势实现手段 虚拟化架构概述寄居虚拟化架构裸金属虚拟化架构操作系统虚拟化架构混合虚拟化架构几种虚拟化架构的比较虚拟化架构与虚拟化技术的关系 虚拟化技术分类服务器虚拟化技术分类 存储虚拟化技术分类网络虚拟化技术分类 服务器虚拟化技术处理器虚…

开源软件 | 一文彻底搞懂许可证的定义、起源、分类及八大主流许可证,让你选型不再头疼

为什么开源软件会存在许可证&#xff0c;许可证的起源与产生目的是为了解决什么问题&#xff1f;许可证的定义又是怎样的&#xff1f;什么是Copyleft&#xff0c;与Copyright有何区别&#xff1f;开源软件常见的许可证有哪些&#xff1f;这些许可证都有什么特点&#xff1f;接下…

Zabbix实现7x24小时架构监控

上篇&#xff1a;https://blog.csdn.net/Lzcsfg/article/details/138774511 文章目录 Zabbix功能介绍Zabbix平台选择安装Zabbix监控端部署MySQL数据库Zabbix参数介绍登录Zabbix WEBWEB界面概览修改WEB界面语言添加被控主机导入监控模板主机绑定模板查看主机状态查看监控数据解…

代码随想录算法训练营第三十四天 | 理论基础、455.分发饼干、376、摆动序列、53.最大子序和

目录 理论基础 455.分发饼干 思路 代码 376.摆动序列 思路 代码 53.最大子序和 思路 代码 理论基础 代码随想录 455.分发饼干 代码随想录 思路 可以是大饼干优先满足大胃口&#xff0c;也可以是小饼干优先满足小胃口。 代码 class Solution:def findContentChildre…