【JUC2022】第七章 AQS、ReentrantReadWriteLock 和 StampedLock
【JUC2022】第七章 AQS
文章目录
- 【JUC2022】第七章 AQS
- 一、AQS
- 1.概述
- 2.同步器
- 3.抽象的
- 4.队列式
- 二、ReentrantReadWriteLock
- 1.概述
- 2.案例
- 3.存在的问题
- 三、StampedLock
- 1.概述
- 2.案例
- 3.存在的问题
一、AQS
1.概述
AQS(AbstractQueueSynchronizer,抽象的队列式同步器),它提供了一个基于 FIFO 队列,可以用于构建锁或者其他相关同步器的基础框架,许多同步类的实现都依赖于它,比如 ReentrantLock、Semaphore、CountDownLatch
AQS 实现了 3 点基本功能:
- 同步器基本范式、结构
- 线程的阻塞、唤醒机制
- 线程阻塞队列的维护
我们来看一下 java.util.concurrent.locks 的类关系图
上图中,Lock 的实现类其实都是构建在 AQS 上的,但为何没用 UML 线表示它们之间的关系呢?这是因为每个 Lock 实现类都持有自己内部类 Sync 的实例,而这个 Sync 才是继承自 AQS 的。那为何要实现不同的 Sync 呢?这是因为不同的 Lock 的用途不同
2.同步器
多线程环境下,线程之间可以通过某种状态来同步,比如只有当状态满足某种条件,才能触发线程执行某种操作,能实现这个特性的可以称之为同步器
AQS 里有一个最关键的属性 private volatile int state,可以理解将它为资源数量的抽象。AQS 提供了 getState 和 setState 方法,还有一个线程安全的 compareAndSetState 方法,它利用了 Unsafe 的 CAS 操作,可以做到在并发场景下对 state 进行原子性的修改,并且可以获得修改结果。正式因为这个特性,可以使用 AQS 构建同步器
3.抽象的
AQS 是一个抽象类,需要被子类继承并且重写其中的一些方法,官方对此做了一些说明:AQS 的子类必须重写那些会更改 state 的 protected 方法,以及定义 state 的何种状态意味着需要阻塞。比如,如果要实现一个锁,则 state 可以定义为:0 表示未锁定,1 表示锁定。如果要实现信号量,state 可以表示资源甚于
为此,AQS 提供了以下方法:
- tryAcquire(int): 试图在独占模式下获取对象的状态
- tryRelaease(int): 试图设置状态来反映独占模式下的释放
- tryAcquireShared(int): 试图在共享模式下获取对象的状态
- tryRelaease(int): 试图设置状态来反映共享模式下的释放
- isHeldExclusively(): 如果对于当前线程,同步是以独占方式进行的,则返回 true
4.队列式
AQS 将阻塞队列线程封装到了一个内部类 Node 里,并维护了一个 CLH Node FIFO 队列。CLH 队列是一个非阻塞的 FIFO 队列,也就是说,往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证原子性,实现无锁且快速的修改操作
以 ReentrantLock 的加锁过程为例:
- 尝试加锁
- 加锁失败,线程加入队列
- 线程入队后进入阻塞状态
二、ReentrantReadWriteLock
1.概述
读写锁定义为:一个资源能够被多个读线程访问,或者被一个写线程访问,并且读写线程不能同时访问
在读多写少的场景下,读写锁具有较高的性能体现
2.案例
package com.sisyphus.ReentrantReadWriteLock;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;class MyResource{Map<String, String> map = new HashMap<>();Lock lock = new ReentrantLock();ReadWriteLock rwLock = new ReentrantReadWriteLock();public void write(String key, String value){rwLock.writeLock().lock();try{System.out.println(Thread.currentThread().getName() + "\t" + "正在写入");map.put(key, value);try{TimeUnit.MILLISECONDS.sleep(500);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + "完成写入" + value);}finally {rwLock.writeLock().unlock();}}public void read(String key){rwLock.readLock().lock();try{System.out.println(Thread.currentThread().getName() + "\t" + "正在读取");String result = map.get(key);try{TimeUnit.MILLISECONDS.sleep(200);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + "完成读取" + result);}finally {rwLock.readLock().unlock();}}
}public class ReentrantReadWriteLockDemo {public static void main(String[] args) {MyResource myResource = new MyResource();for(int i = 0; i < 10; i++){int finalI = i;new Thread(()->{myResource.write(finalI + "", finalI + "");},String.valueOf(i)).start();}for(int i = 0; i < 10; i++){int finalI = i;new Thread(()->{myResource.read(finalI + "");},String.valueOf(i)).start();}for(int i = 0; i < 3; i++){int finalI = i;new Thread(()->{myResource.write(finalI + "", finalI + "");},"新写线程" + String.valueOf(i)).start();}}
}
3.存在的问题
写线程饥饿
当读锁被获取时,写锁无法获取,必须等待读锁释放。如果读线程太多,那么写线程将一直被阻塞,使用“公平锁”可以一定程度上缓解这个问题,但是“公平锁”是以牺牲系统吞吐量为代价的
锁降级
锁的颗粒度减小叫锁升级,锁的颗粒度增大叫锁降级。遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级为读锁。但是无法从读锁升级为写锁,这也是会造成写线程饥饿的原因
三、StampedLock
1.概述
StampedLock 是 JDK1.8 中新增的一个读写锁,是对 JDK1.5 中的 ReentrantReadWriteLock 的优化
StampedLock 有一个重要属性 long stamp,代表了锁的状态。所有获取锁的方法,都会返回一个 stamp,stamp 为 0表示失败;所有释放锁的方法,都需要传入一个 stamp,并且这个 stamp 必须是和成功获取锁得到的 stamp 一致
StampLock 是不可重入的,如果一个线程已经持有了写锁,再去获取写锁将会导致死锁
StampLock 有三种访问模式:
- Reading(悲观读模式):功能和 ReentrantReadWriteLock 的读锁类似
- Writeing(写模式):功能和 ReentrantReadWriteLock 的写锁类似
- Optimistic reading(乐观读模式):无锁机制,支持读写并发,先乐观地认为读取时没人修改,判断被修改再升级为悲观读模式
2.案例
package com.sisyphus.StampedLockDemo;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;public class StampedLockDemo {static int number = 1;static StampedLock stampedLock = new StampedLock();public void write(){long stamp = stampedLock.writeLock();System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");try{number = number + 1;}finally {stampedLock.unlock(stamp);}System.out.println(Thread.currentThread().getName() + "\t" + "写线程结束修改");}//悲观读public void read(){long stamp = stampedLock.readLock();System.out.println(Thread.currentThread().getName() + "\t" + "come in readLock codeBlock lock, 4 seconds continue ...");for (int i = 0; i < 4; i++){try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + "正在读取中……");}try{int result = number;System.out.println(Thread.currentThread().getName() + "\t" + "获得成员变量值result:" + result);System.out.println("写线程没有修改成功,读锁未释放,写锁无法接入,传统的读写互斥");}finally {stampedLock.unlockRead(stamp);}}//乐观读public void tryOptimisticRead(){long stamp = stampedLock.tryOptimisticRead();int result = number;//故意间隔 4 秒钟,乐观认为读取过程中没有其他线程修改过 number 值System.out.println("4 秒前 stampedLock.validate 方法值(true无修改,false有修改)" + "\t" + stampedLock.validate(stamp));for(int i = 0; i < 4; i++){try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + "正在读取..." + i + " 秒后 stampedLock.validate 方法值(true无修改,false有修改)" + stampedLock.validate(stamp));}if(!stampedLock.validate(stamp)){System.out.println("有人修改过-----有写操作");stamp = stampedLock.readLock();try{System.out.println("从乐观读升级为悲观读");result = number;System.out.println("重新悲观读后 result:" + result);}finally {stampedLock.unlockRead(stamp);}}System.out.println(Thread.currentThread().getName() + "\t" + " finally value:" + result);}public static void main(String[] args) {StampedLockDemo resource = new StampedLockDemo();/*传统版new Thread(()->{resource.read();},"readThread").start();try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}new Thread(()->{System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");resource.write();},"writeThread").start();try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "\t" + "number:" + number);*/new Thread(()->{resource.tryOptimisticRead();},"readThread").start();//暂停 2 秒钟,演示读过程可以介入写操作try{TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}new Thread(()->{System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");resource.write();},"writeThread").start();}
}
3.存在的问题
- StampedLock 不支持重入
- StampedLock 的悲观读锁和写锁都不支持条件变量 Condition
相关文章:

【JUC2022】第七章 AQS、ReentrantReadWriteLock 和 StampedLock
【JUC2022】第七章 AQS 文章目录【JUC2022】第七章 AQS一、AQS1.概述2.同步器3.抽象的4.队列式二、ReentrantReadWriteLock1.概述2.案例3.存在的问题三、StampedLock1.概述2.案例3.存在的问题一、AQS 1.概述 AQS(AbstractQueueSynchronizer,抽象的队列式同步器)&am…...

Spark 磁盘作用
Spark 磁盘作用磁盘作用性能价值失败重试ReuseExchangeSpark 导航 磁盘作用 临时文件、中间文件、缓存数据,都会存储到 spark.local.dir 中 在 Shuffle Map 时, 当内存空间不足,就会溢出临时文件存储到磁盘上溢出的临时文件一起做归并计算…...

三、Spark 内存管理
文章目录Spark 内存管理堆内和堆外内存堆内内存堆外内存堆外与堆内的平衡内存空间分配静态内存管理(早期版本)统一内存管理Spark 内存管理 堆内和堆外内存 Spark 引入了堆外(Off-heap)内存,使之可以直接在工作节点的…...

Java 面试常见项目问题回答
之前整理了好几期,我面试时遇到的面试候选人,我是如何我去筛选的,这一期,我们来看下一些 面试常问的业务性的问题 你们公司权限认证是如何实现的? 这其实是个通用性的问题,大部分公司 小型公司,或者中型公…...

文件上传和下载(原生JS + SpringBoot实现)
目录 概述 前端编写-上传表单和图片回显 HTML表单代码 发送请求逻辑 CSS代码 后端编写-文件上传接口 后端编写-文件下载接口 概述 在现代Web应用程序中,文件上传和下载是常见的功能。本博客将介绍如何使用原生JS和Spring Boot实现文件上传和下载的功能。 在其…...
【C语言学习笔记】:安全性
用const修饰变量或方法,从而告诉编译器这些都是不可变的,有助于编译器优化代码,并帮助开发人员了解函数是否有副作用。此外,使用const &可以防止编译器复制不必要的数据。John Carmack对const的评论[2]值得一读。 // Bad Ide…...

Linux - 磁盘存储管理 磁盘引入
# 我们要介绍下 磁盘管理, 那不妨先来看一张图来简单 引入 :这张图呢,是我们 Windows 上的磁盘管理的显示 。根据这幅图呢,提出一个问题 :>>> 这幅图磁盘管理所显示的内容,你能判断出 该电脑 有几…...
分割std::string成多个string
文章目录问题描述前置知识解决代码问题描述 假设我们有一个http服务器,此服务器接收客户端发来的http请求,假设请求如下 GET / HTTP/1.1我们怎么将这个Http请求分割成三份,分别存入不同的string中分别处理? 前置知识 首先std::string的本…...
3月多国更新进出口产品规定
【3月多国更新进出口产品规定】2023年3月多项外贸新规实施,涉及欧盟,伊拉克,泰国,孟加拉国,埃及等多国进出口产品限制及海关税则。1. 3月1日起给予埃塞俄比亚等三国98%税目产品零关税待遇中国国务院关税税则委员会17日…...
nacos相关面试题
Nacos是阿里巴巴开源的一款注册中心和配置中心,它能够实现服务的注册、发现和配置管理等功能。Nacos的实现原理主要分为以下几个部分:注册中心:Nacos作为注册中心,通过提供RESTful API的方式对外提供注册和发现服务。它使用基于Ra…...

Linux基础命令-groupmems管理组群的成员
Linux-usermod修改用户 Linux-useradd创建用户 Linux-userdel删除用户 Linux基础命令-chown修改文件属主 Linux基础命令-chmod修改文件权限 groupmems 命令介绍 先来看看这个命令的帮助信息是什么概念 NAME groupmems - administer members of a user’s primary group group…...

css系统化学习
元素的语义化 SEO:搜索引擎优化 根据搜索引擎展示的规律,语义化的元素更容易被展示获得更多浏览量 字符编码 css历史 内联样式(inline) style"内容全写在等号后面,双引号里面,多个之间用;隔开" 内部样式(internal) style写在head里面,在title下面,不是在body内, …...

AI的简单介绍
什么是AI? AI 是 Artificial Intelligent 的缩写,是我们通常意义上说的人工智能。 简单来说就是让机器能够模拟人类的思维能力,让它能够像人一样感知、思考甚至决策。 为什么要开发AI? 因为在过去,都是我们学习机器…...

【Linux】-- 进程间通讯
目录 进程间通讯概念的引入 意义(手段) 思维构建 进程间通信方式 管道 站在用户角度-浅度理解管道 匿名管道 pipe函数 站在文件描述符角度-深度理解管道 管道的特点总结 管道的拓展 单机版的负载均衡 匿名管道读写规则 命名管道 前言 原理…...

STM32模拟SPI时序控制双路16位数模转换(16bit DAC)芯片DAC8552电压输出
STM32模拟SPI时序控制双路16位数模转换(16bit DAC)芯片DAC8552电压输出 STM32部分芯片具有12位DAC输出能力,要实现16位及以上DAC输出需要外挂DAC转换ASIC。 DAC8552是双路16位DAC输出芯片,通过SPI三线总线进行配置控制输出。这里…...

基于intel x86+fpga智能驾驶舱和高级驾驶辅助系统硬件设计(二)
系统功能架构及各模块功能介绍 智能驾驶舱和高级驾驶辅助系统是一个车载智能终端嵌入式平台,系统是一个能够运行 虚拟化操作系统的软件和硬件的综合体。本文的车载主机包括硬件主控处理器、电源管理芯 片、存储设备、输入输出控制器、数字仪表系统系统、后座娱乐系统…...

oneblog_justauth_三方登录配置【Github】
文章目录oneblog添加第三方平台github中创建三方应用完善信息登录oneblog添加第三方平台 1.oneblog管理端,点击左侧菜单 网站管理——>社会化登录配置管理 ,添加一个社会化登录 2.编辑信息如下,选择github平台后复制redirectUri,然后去github获取cl…...
自行车轮胎充气泵PCBA方案
轮胎充气泵PCBA方案由多种元器件设计组合而成,PCBA是英文Printed Circuit Board Assembly 的简称,也就是说PCB空板经过SMT上件,或经过DIP插件的整个制程,简称PCBA。PCBA是一个电子产品功能实现的最原始的状态,未经过任…...
200 22222
101. blob.png 新到组织的项目经理被分配管理一个具有多名干系人的项目。项目经理希望确定哪些干系人是内部的,哪些干系人是外部的。若要了解干系人的角色,项目经理应该查阅哪一份文件? A. 干系人登记册 B. 干系人分析 C. 干系人管理计划 D.…...

<JVM上篇:内存与垃圾回收篇>13 - 垃圾回收器
笔记来源:尚硅谷 JVM 全套教程,百万播放,全网巅峰(宋红康详解 java 虚拟机) 文章目录13.1. GC 分类与性能指标13.1.1. 垃圾回收器概述13.1.2. 垃圾收集器分类13.1.3. 评估 GC 的性能指标13.2. 不同的垃圾回收器概述13.…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...