深入剖析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]; }; 有些编译器会报错无法编译可以改为:…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
