深入剖析Java并发库(JUC)之StampedLock的应用与原理
在现代多核处理器架构下,并发编程成为提升程序性能的关键手段。Java作为一门广泛使用的编程语言,提供了丰富的并发编程工具和库,其中Java并发库(JUC)就是非常重要的一部分。在JUC中,除了我们熟知的ReentrantLock、ReentrantReadWriteLock等锁机制外,还有一个相对较新的锁机制——StampedLock。本文将深入解析StampedLock的工作原理、使用场景以及它相比其他锁机制的优势。
目录
- 一、StampedLock简介
- 二、StampedLock的工作机制
- 三、StampedLock的原理
- 3.1 StampedLock核心
- 3.2 源码分析
- 四、StampedLock的使用场景
- 五、StampedLock的使用
- 六、StampedLock与其他锁机制的比较
- 总结
一、StampedLock简介
StampedLock是Java 8引入的一种新的锁机制,它提供了乐观读锁和悲观读写锁的能力。与传统的ReentrantLock和ReentrantReadWriteLock相比,StampedLock在并发性能上有了显著的提升。这是因为它支持一种称为“乐观读”的锁策略,该策略允许多个线程同时读取共享资源,而无需阻塞或等待其他线程的锁释放。
二、StampedLock的工作机制
StampedLock内部维护了一个状态变量,用于表示锁的状态。这个状态变量不仅包含了锁的类型(读锁或写锁),还包含了一个版本号(stamp)。当线程尝试获取锁时,StampedLock会根据锁的类型和当前状态来决定是否授予锁,并返回一个相应的stamp值。线程在释放锁时,需要传入之前获得的stamp值,以确保锁的正确释放。
StampedLock提供了两种类型的读锁:乐观读锁和悲观读锁。乐观读锁允许多个线程同时读取共享资源,而无需阻塞或等待。这种锁策略适用于读多写少的场景,可以显著提高并发性能。然而,如果有一个线程正在修改共享资源,那么乐观读锁可能会读取到不一致的数据。为了避免这种情况,StampedLock还提供了悲观读锁,它在读取共享资源时会阻塞其他写线程的访问。
StampedLock 是 Java 并发包 java.util.concurrent.locks 中的一个类,它提供了乐观读、悲观读和写锁的机制。由于 StampedLock 的实现相对复杂,这里我将简要概述其核心原理,并提供一些关键部分的源码分析。请注意,源码可能会随着 Java 版本的更新而有所变化,以下分析基于 Java 8 及之后的版本。
三、StampedLock的原理
3.1 StampedLock核心
-
锁状态:StampedLock 使用一个内部变量(通常是一个 long 类型的变量)来维护锁的状态。这个状态不仅表示锁是否被持有,还包含了一个版本号(stamp),用于支持乐观读锁。
-
乐观读锁:当线程尝试获取乐观读锁时,StampedLock 会检查当前是否有写锁被持有。如果没有,它会增加一个读锁计数器并返回一个 stamp(通常是当前状态的一个快照)。乐观读锁不会阻塞其他读线程或写线程,但可能在写线程获得锁后读取到不一致的数据。
-
悲观读锁:与乐观读锁不同,悲观读锁会阻塞其他写线程的访问。当线程尝试获取悲观读锁时,StampedLock 会检查是否有其他写线程持有锁或正在等待锁。如果没有,它会授予锁并返回一个 stamp。
-
写锁:写锁是独占的,意味着同一时间只能有一个线程持有写锁。当线程尝试获取写锁时,StampedLock 会检查是否有其他读锁或写锁被持有。如果有,线程将被阻塞直到锁被释放。
-
可重入性:StampedLock 支持锁的可重入性,即一个线程可以多次获得同一个锁而不会导致死锁。这是通过跟踪每个线程的锁持有计数来实现的。
-
锁转换:StampedLock 允许线程将乐观读锁转换为悲观读锁或写锁,或将悲观读锁转换为写锁,前提是在转换过程中没有其他线程获得相应的锁。
3.2 源码分析
由于 StampedLock 的源码较长且复杂,这里只展示和分析一些关键部分。
锁状态变量
StampedLock 使用一个名为 state
的 long 类型变量来存储锁的状态。这个状态包含了锁的类型(读锁、写锁)和版本号等信息。
private final long WRITER_MASK = 0x8000000000000000L; // 写锁标志位
private final long NOT_LOCKED = 0L; // 锁未被持有的状态
private volatile long state; // 锁状态变量
乐观读锁获取
当线程尝试获取乐观读锁时,会调用 tryOptimisticRead
方法:
public long tryOptimisticRead() {long s = state; // 获取当前锁状态// 检查是否有写锁被持有(通过检查最高位是否为1)if ((s & WRITER_MASK) != 0L) {// 有写锁被持有,返回0表示获取失败return 0L;} else {// 没有写锁被持有,返回当前状态作为stamp(乐观读锁不会改变锁状态)return s;}
}
写锁获取
当线程尝试获取写锁时,会调用类似 writeLock
或 tryWriteLock
的方法,这些方法最终会调用一个内部方法来实现锁的获取逻辑。以下是一个简化的示例:
private boolean acquireWrite(boolean interruptible, long deadline) {// 省略部分代码...long s = state, next; // 当前状态和下一个状态// 循环尝试获取锁直到成功或超时或中断while (((s & WRITER_MASK) != 0L) || ((next = tryIncWriter(s)) == 0L)) {// 锁被其他线程持有,根据interruptible和deadline决定等待或返回失败// 省略等待和中断处理逻辑...}// 成功获取写锁,设置锁持有者信息(线程和重入计数)并返回true// 省略设置锁持有者信息和返回逻辑...
}
tryIncWriter
会尝试增加写锁计数器并返回新的状态。如果返回 0,表示获取锁失败(通常是因为锁已经被其他线程持有或状态已经改变)。注意这里的循环和等待逻辑是为了处理并发访问和锁竞争的情况。
四、StampedLock的使用场景
StampedLock适用于读多写少、数据一致性要求不高的场景。例如,在一个缓存系统中,多个线程可能同时读取同一个缓存项,而只有少数线程会修改缓存项。在这种情况下,使用StampedLock的乐观读锁可以显著提高并发性能。然而,如果数据一致性要求非常高,或者写操作非常频繁,那么可能需要考虑使用其他的锁机制,如ReentrantLock或ReentrantReadWriteLock。
五、StampedLock的使用
下面的代码展示了如何使用乐观读锁、悲观读锁和写锁。注意下,这只是一个基础示例,用于说明各种锁的使用方式。
import java.util.concurrent.locks.StampedLock;public class StampedLockExample {// 创建一个 StampedLock 实例private final StampedLock stampedLock = new StampedLock();// 共享资源private int balance = 0;// 使用乐观读锁读取余额public int getBalanceWithOptimisticReadLock() {// 尝试获取乐观读锁long stamp = stampedLock.tryOptimisticRead();// 读取余额int currentBalance = balance;// 检查乐观读锁在读取过程中是否被无效(比如被写锁干扰)if (!stampedLock.validate(stamp)) {// 如果无效,则使用悲观读锁重新读取stamp = stampedLock.readLock();try {currentBalance = balance;} finally {// 释放悲观读锁stampedLock.unlockRead(stamp);}}return currentBalance;}// 使用悲观读锁读取余额public int getBalanceWithPessimisticReadLock() {// 获取悲观读锁long stamp = stampedLock.readLock();try {// 读取余额return balance;} finally {// 释放悲观读锁stampedLock.unlockRead(stamp);}}// 使用写锁更新余额public void updateBalanceWithWriteLock(int amount) {// 获取写锁long writeStamp = stampedLock.writeLock();try {// 更新余额balance += amount;} finally {// 释放写锁stampedLock.unlockWrite(writeStamp);}}public static void main(String[] args) {StampedLockExample example = new StampedLockExample();// 模拟多线程环境下的读写操作Runnable readTask = () -> {int balance = example.getBalanceWithOptimisticReadLock();System.out.println("读取到的余额(乐观读锁): " + balance);};Runnable writeTask = () -> {example.updateBalanceWithWriteLock(100);System.out.println("更新了余额(写锁), 新余额: " + example.getBalanceWithPessimisticReadLock());};// 启动多个读线程和写线程来模拟并发访问// 注意:在实际应用中,应该控制线程的数量和执行顺序以避免过度竞争和潜在的死锁风险。// 这里为了简化示例,并没有使用线程池或同步工具来控制线程的启动和终止。new Thread(readTask).start();new Thread(readTask).start();new Thread(writeTask).start();// ... 可以继续启动更多线程进行测试}
}
在上面的代码中,我们有一个 balance
变量作为共享资源。我们定义了三个方法:
-
getBalanceWithOptimisticReadLock
:使用乐观读锁尝试读取余额。如果在读取过程中乐观读锁被写锁干扰而失效,它将回退到使用悲观读锁重新读取余额。 -
getBalanceWithPessimisticReadLock
:使用悲观读锁读取余额。这将阻止其他写线程在此期间修改余额,但允许多个读线程同时读取。 -
updateBalanceWithWriteLock
:使用写锁更新余额。这将独占访问共享资源,确保在更新期间没有其他线程能够读取或写入余额。
在 main
方法中,我们创建了一个 StampedLockExample
实例,并定义了读任务和写任务来模拟多线程环境下的读写操作。然后,我们启动多个线程来执行这些任务。
六、StampedLock与其他锁机制的比较
与传统的ReentrantLock和ReentrantReadWriteLock相比,StampedLock在并发性能上有了显著的提升。这是因为它采用了乐观读锁的策略,允许多个线程同时读取共享资源。
此外,StampedLock还支持可重入锁和公平锁的特性,提供了更灵活的锁控制选项。
然而,StampedLock的使用也相对复杂一些,需要开发者对锁的状态和版本号进行精细的控制和管理。
总结
StampedLock是Java并发库(JUC)中一种高效、灵活的锁机制。它提供了乐观读锁和悲观读写锁的能力,适用于读多写少、数据一致性要求不高的场景。与传统的ReentrantLock和ReentrantReadWriteLock相比,StampedLock在并发性能上有了显著的提升。然而,它的使用也相对复杂一些,需要开发者对锁的状态和版本号进行精细的控制和管理。在实际应用中,开发者应根据具体的场景和需求选择合适的锁机制来确保程序的正确性和性能。
术因分享而日新,每获新知,喜溢心扉。
诚邀关注公众号 『码到三十五
』 ,获取更多技术资料。
相关文章:

深入剖析Java并发库(JUC)之StampedLock的应用与原理
码到三十五 : 个人主页 心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得 ! 在现代多核处理器架构下,并发编程成为提升程序性能的关键手段。Java作为一门广泛使用的编程语言,提供了丰…...
【PMP】每日一练2
项目生命周期与开发生命周期 项目生命周期开发生命周期 项目生命周期 项目生命周期:描述项目从开始到结束所经历的一系列阶段。 项目生命周期类型: 预测型:也称瀑布型生命周期。在生命周期的早期阶段就确定了项目的范围、时间、成本。客户需…...

2024年投影仪显示技术怎么选?哪个好?优缺点详解,买前必看
日前,华为海思LCoS激光投影技术引发了众多关注,该技术的面世,或将在投影行业掀起新的浪潮!众所周知,目前主流的显示技术主要是DLP、3LCD和1LCD几种。那么,这几种技术之间到底有什么区别?下面就带…...

Git Bash命令初始化本地仓库,提交到远程仓库
git init:初始化空仓库 // 初始化一个空仓库或者重新初始化一个存在的仓库 git init git remote // 为当前本地仓库添加一个远程仓库地址 git remote add origin https://gitee.com/xxx/demo.git git pull // 从设置好链接的远程仓库拉去已经存在的数据,…...

Docker 学习笔记一
一、什么是docker Docker 是一个基于轻量级虚拟化技术的容器,整个项目基于Go语言开发;Docker是一个C/S架构,后端众多模块各司其职,docker的daemon是运行在主机上通过client可以进行通信。 docker 由三部分组成:镜像(…...
Git一点通
1.Git的优势 Git是一个伟大的版本管理工具,比之svn,具有以下优势: 分布式版本控制:Git是一种分布式版本控制系统,每个开发者都拥有自己的完整代码库,不需要依赖网络连接就可以进行版本控制、合并和提交操作…...
商标转让有哪些好处 商标转让条件 商标转让流程
商标转让是企业之间转让商标权益的一种交易方式。它在商业运作中扮演着重要的角色,对于企业的发展和战略布局都有一定的影响。 商标转让的好处: 1、商标价值变现:企业在商标注册过程中投入了大量的时间和精力,通过转让可以将商标…...

诺视科技完成亿元Pre-A2轮融资,加速Micro-LED微显示芯片商业化落地
近日,Micro-LED微显示芯片研发商诺视科技(苏州)有限公司(以下简称“诺视科技”)宣布完成亿元Pre-A2轮融资,本轮融资由力合资本领投,老股东盛景嘉成、汕韩基金以及九合创投持续加码,这…...

Unity定时播放音乐
一、需求 需要定时在早上8:50,中午12:00,下午13:10定时播放音乐 二、实现步骤 依次在unity创建背景图、主文字提示、时间文字提示、音量控制器及音量文字提示、退出按钮、播放按钮,暂停按钮 在Canvas下创建一个Script脚本:获取…...

如何做接口测试?
今天来聊聊接口测试,现在是2024年了,打开招聘网站随便点开一个招聘帖子,几乎都可以看到岗位JD要求写着有接口测试经验优先。其重要性可见一斑! 目前,凡是好一点稍具规模的公司哪怕是大厂外包也几乎都要求会接口测试&a…...

U盘打不开提示格式化怎么办,U盘提示格式化数据恢复
U盘打不开提示格式化怎么办?在使用U盘的过程中,有时候我们可能会遇到U盘打不开的情况,并且提示需要格式化才能继续使用。这种情况下,我们应该如何处理呢?U盘承载着无数重要的数据,里面存放着是大家多年辛勤努力的结晶,这些文件见证很多东西。突然打不开,并提示格式化,…...

LeetCode - 存在重复元素
219. 存在重复元素 II 这道题可以用两个方法解决。 哈希表 从左到右遍历数组,并将数组的下标存到hash中,在遍历数字的过程中,如果hash中不存在nums[i],将nums[i]加入到hash当中,若存在,则判断下标之间的关…...

RUST egui体验
egui官方提供了web版的demo,效果还是很不错的,就是用的时候有点一头雾水,没有找到明确的指导怎么把这些组件插入到自己的application或者web。花了一天时间撸了一遍流程,记录一下,说不定以后能用到呢 >_< efram…...

详解llamaindex
什么是LlamaIndex LlamaIndex是一个用于LLM应用程序的数据框架,用于注入、结构化,并访问私有或特定领域的数据。 入门教程 简单使用 # Linux export OPENAI_API_KEYxxxwindows set OPENAI_API_KEYxxx# 代码中加入 API_SECRET_KEY "xxx" B…...
管理类联考–复试–英文面试–问题--规划介绍原因做法--汇总
文章目录 规划介绍原因做法 规划 一、提问方式:问题1:读研的规划;问题2:未来五年的规划;问题3:是否计划读博 常见问法1:Can you talk about your plans in the postgraduate period?…...

成都百洲文化传媒有限公司电商新浪潮的领航者
在当今电商行业风起云涌的时代,成都百洲文化传媒有限公司以其独特的视角和专业的服务,成为了众多商家争相合作的伙伴。今天,就让我们一起走进百洲文化的世界,探索其背后的成功密码。 一、百洲文化的崛起之路 成都百洲文化传媒有限…...

【Unity】获取游戏对象或组件的常用方法
前言 在Unity开发过程中,我们经常需要获取组件,那么在Unity里如何获取组件呢? 一、获取游戏对象 1.GameObject.Find GameObject.Find 是通过物体的名称获取对象的 所以会遍历当前整个场景,效率较低 而且只能获取激活状态的物体…...

html5cssjs代码 024 响应式布局示例
html5&css&js代码 024 响应式布局示例 一、代码二、解释 该HTML代码重点在于构建一个带有响应式设计的两栏布局网页,包含页头、导航条、主要内容区(左右两列)和底部区域,并运用CSS样式设置页面元素的布局、颜色、字体、间…...
json详解
文章目录 概述JSON 发展史什么是 JSON为什么要使用 JSONJSON 的不足JSON 应该如何存储什么时候会使用 JSON1) 定义接口2) 序列化3) 生成 Token4) 配置文件 Json分类json-lib开源的JacksonGoogle的Gson阿里巴巴的FastJsonJSON.simple JSON 序列化方式有哪些消息队列中传输的数据…...
C语言之---柔性数组
1.1前记 也许你从来没有听说过柔性数组这个概念,但是它是确实存在的。 C99中,结构中的最后一个元素允许是未知大小的数组,这就是柔性数组成员。 例如: struct st_type {int i;int a[0]; }; 有些编译器会报错无法编译可以改为:…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...