Java多线程-StampedLock(原子读写锁)

StampedLock 是读写锁的实现,对比 ReentrantReadWriteLock 主要不同是该锁不允许重入,多了乐观读的功能,使用上会更加复杂一些,但是具有更好的性能表现。StampedLock 的状态由版本和读写锁持有计数组成。 获取锁方法返回一个邮戳,表示和控制与锁状态相关的访问; 这些方法的“尝试”版本可能会返回特殊值 0 来表示获取锁失败。 锁释放和转换方法需要邮戳作为参数,如果它们与锁的状态不匹配则失败。

但是也是由于 StampedLock 大量使用自旋的原因(ReentrantReadWriteLock 也使用了自旋,但是没有 StampedLock 频繁),CPU 的消耗理论上也比 ReentrantReadWriteLock 高。

StampedLock 非常适合写锁中的操作非常快的业务场景。因为读锁如果因为写锁而获取锁失败,读锁会做重试获取和有限次的自旋的方式,比较晚进入到等待队列中。如果在自旋过程中,写锁能释放,那么获取读锁的线程就能避免被操作系统阻塞和唤醒等耗资源操作,增加读锁的响应效率。

三种模式

悲观读锁

与 ReentrantReadWriteLock 的读锁类似,多个线程可以同时获取悲观读锁。这是一个共享锁,允许多个线程同时读取共享资源。

乐观读锁

相当于直接操作数据,不加任何锁。在操作数据前并没有通过 CAS 设置锁的状态,仅仅通过位运算测试。如果当前没有线程持有写锁,则简单地返回一个非 0 的 stamp 版本信息。返回 0 则说明有线程持有写锁。获取该 stamp 后在具体操作数据前还需要调用 validate 方法验证该 stamp 是否己经不可用。

写锁

与 ReentrantReadWriteLock的写锁类似,写锁和悲观读锁是互斥的。虽然写锁与乐观读锁不会互斥,但是在数据被更新之后,之前通过乐观读锁获得的数据已经变成了脏数据,需要自己处理这个。

StampedLock 的读写锁都是不可重入锁,所以在获取锁后释放锁前不应该再调用会获取锁的操作,以避免造成调用线程被阻塞。

在实际应用中,StampedLock 可以用于那些读操作远多于写操作的场景,例如缓存系统、数据报表生成等。在这些场景中,StampedLock 可以显著提高并发性能,同时保证数据的一致性和安全性。

最重要的一点: 在使用时需要特别注意:如果某个线程阻塞在StampedLock的readLock()或者writeLock()方法上时,此
时调用阻塞线程的interrupt()方法中断线程,会导致CPU飙升到100%。

所以尽量在写操作是非常快的场景下使用, 这样读的时候乐观锁释放的非常快,几乎达到无锁模式。

所有接口方法

image.png
image.png
image.png

经典案例

import java.util.concurrent.locks.StampedLock;public class StampedLockExample {private int inventory = 100; // 初始库存为100private final StampedLock lock = new StampedLock();// 扣减库存操作public void decreaseInventory(int quantity) {long stamp = lock.writeLock(); // 获取写锁try {if (inventory >= quantity) {inventory -= quantity; // 扣减库存System.out.println("成功减少库存 " + quantity + ", 当前的库存量: " + inventory);} else {System.out.println("未能减少库存,库存不足");}} finally {lock.unlockWrite(stamp); // 释放写锁}}// 获取当前库存public int getInventory() {long stamp = lock.tryOptimisticRead(); // 乐观读锁int currentInventory = inventory;if (!lock.validate(stamp)) { // 检查乐观读锁是否有效stamp = lock.readLock(); // 乐观读锁无效,转为悲观读锁try {currentInventory = inventory; // 获取当前库存} finally {lock.unlockRead(stamp); // 释放读锁}}return currentInventory; // 返回当前库存}public static void main(String[] args) {StampedLockExample manager = new StampedLockExample();// 多个线程同时扣减库存Thread t1 = new Thread(() -> {manager.decreaseInventory(20); // 线程1扣减库存System.out.println(manager.getInventory());});Thread t2 = new Thread(() -> {manager.decreaseInventory(50); // 线程2扣减库存System.out.println(manager.getInventory());});t1.start();t2.start();}
}

官网案例

 
public class Point {private double x, y;private final StampedLock sl = new StampedLock();public void move(double deltaX, double deltaY) {使用写锁-独占操作,并返回一个邮票long stamp = sl.writeLock();try {x += deltaX;y += deltaY;} finally {使用邮票来释放写锁sl.unlockWrite(stamp);      }}// 使用乐观读锁访问共享资源// 注意:乐观读锁在保证数据一致性上需要拷贝一份要操作的变量到方法栈,并且在操作数据时候可能其 // 他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是 // 最新的数据,但是一致性还是得到保障的。public double distanceFromOrigin() {使用乐观读锁-并返回一个邮票,乐观读不会阻塞写入操作,从而解决了写操作线程饥饿问题。long stamp = sl.tryOptimisticRead();    拷贝共享资源到本地方法栈中double currentX = x, currentY = y;      if (!sl.validate(stamp)) {              如果验证乐观读锁的邮票失败,说明有写锁被占用,可能造成数据不一致,所以要切换到普通读锁模式。stamp = sl.readLock();             try {currentX = x;currentY = y;} finally {sl.unlockRead(stamp);}}// 如果验证乐观读锁的邮票成功,说明在此期间没有写操作进行数据修改,那就直接使用共享数据。return Math.sqrt(currentX * currentX + currentY * currentY);}// 锁升级:读锁--> 写锁public void moveIfAtOrigin(double newX, double newY) { // upgrade// Could instead start with optimistic, not read modelong stamp = sl.readLock();try {while (x == 0.0 && y == 0.0) {读锁转换为写锁long ws = sl.tryConvertToWriteLock(stamp); if (ws != 0L) {如果升级到写锁成功,就直接进行写操作。stamp = ws;x = newX;y = newY;break;} else {//如果升级到写锁失败,那就释放读锁,且重新申请写锁。sl.unlockRead(stamp);stamp = sl.writeLock();}}} finally {//释放持有的锁。sl.unlock(stamp);}}}

StampedLock和ReentrantReadWriteLock之间的区别

  1. 锁的类型与特性
    • StampedLock:提供了乐观读、悲观读和写锁三种模式。乐观读模式允许在写锁未被持有时进行无锁读取,通过验证戳记(stamp)来确保数据的一致性。这种模式减少了锁的竞争,提高了吞吐量。
    • ReentrantReadWriteLock:允许多个读线程同时访问,但写线程在访问时必须独占。它支持锁的重入,即同一线程可以多次获取同一把锁。
  2. 性能
    • StampedLock:通常比ReentrantReadWriteLock具有更高的性能,特别是在读多写少的场景下。由于乐观读的存在,它能够在无竞争的情况下避免不必要的锁开销。
    • ReentrantReadWriteLock:在读操作远多于写操作的场景中表现良好,但写锁的饥饿问题和锁降级操作可能影响其性能。
  3. 实现机制
    • StampedLock:并非基于AQS(AbstractQueuedSynchronizer)实现,而是使用了自己的同步等待队列和状态设计。其状态为一个long型变量,与ReentrantReadWriteLock的设计不同。
    • ReentrantReadWriteLock:基于AQS实现,通过内部维护的读写锁来实现多线程间的同步。
  4. 使用场景
    • StampedLock:更适合于读多写少且对性能要求较高的场景,尤其是当数据争用不严重时。它能够有效减少锁的竞争,提高系统的吞吐量。
    • ReentrantReadWriteLock:适用于需要重入锁或需要在写操作后降级为读锁的场景。它提供了更严格的访问控制,但可能在某些情况下牺牲了一定的性能。
  5. 锁的获取与释放
    • StampedLock:在获取锁时会返回一个戳记(stamp),用于后续的锁释放或转换。这个戳记代表了锁的状态,有助于在释放锁时验证数据的一致性。
    • ReentrantReadWriteLock:没有戳记的概念,锁的获取和释放相对简单直接。

综上所述,StampedLock和ReentrantReadWriteLock各有其特点和适用场景。在选择使用哪种锁时,应根据具体的应用需求和性能要求来做出决策。

点赞 -收藏 -关注
有问题在评论区或者私信我-收到会在第一时间回复

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

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

相关文章

LogicFlow 学习笔记——4. LogicFlow 基础 边 Edge

边 Edge 和节点一样&#xff0c;LogicFlow 也内置一些基础的边。LogicFlow 的内置边包括&#xff1a; 直线 - line直角折现 - polyline贝塞尔曲线 - bezier 新建 src/views/Example/LogicFlow/Example08.vue 并编写如下代码&#xff1a; <script setup lang"ts&quo…

图解系列 图解Kafka之Consumer

文章目录 术语消息主题和分区集群和分区副本消费者组重新平衡组/分区再均衡消费者的分区策略群组协调者Coordinator 和群组领导者 Group Leader 流程初始化流程消费流程Consumer重平衡 消费者核心配置示例代码高级提交偏移量的几种方式自动提交手动同步提交手动异步提交提交特定…

深入解析 Java 标准库:构建高效应用的基石

Java 标准库&#xff0c;也称为 Java API&#xff0c;是一组预先编写的类和接口&#xff0c;为 Java 应用程序提供了一系列丰富的功能。这些库被组织成包&#xff08;packages&#xff09;&#xff0c;每个包都包含了一组相关的类和接口&#xff0c;用于处理特定的任务&#xf…

Python使用策略模式实现绘图功能

策略模式&#xff08;Strategy Pattern&#xff09;:允许定义一系列算法&#xff0c;将它们封装起来&#xff0c;使得它们可以互换。 实现绘制不同类型的图表&#xff08;如折线图、柱状图和饼图&#xff09;功能。 下面是一个示例&#xff0c;展示如何传入横坐标和纵坐标内容…

人工智能技术应用笔记(十二):搭建自带大模型微信,完美对接GPT-4o,Kimi等大模型,智能体平台Coze也能接

许多朋友对如何搭建自己的微信机器人非常感兴趣。今天就来教大家如何操作。 一、 准备工作 一台电脑或者云服务器&#xff0c;对配置要求不高&#xff0c;一般的电脑就行 大模型API调用的Key&#xff0c;比如GPT-4o&#xff0c;Kimi&#xff0c;Deepseek&#xff08;不知道怎…

使用Omnipeek进行Wifi/P2P抓包

前言 工作中解决Mirracast投屏连接失败的问题时&#xff0c;遇到了需要抓取wifi数据包的情况&#xff0c;记录一下配置和使用过程。 一、Omnipeek 的安装和配置 1.1 Omnipeek 安装 双击 setup.exe 进行安装 1.2 注册 注册时&#xff0c;版本写 71 &#xff0c;产生序列号和…

代码随想录算法训练营第36期 last day

最后一次更新&#xff0c;之后去复习专业课和简历 583两个字符串的删除操作 自己做出来了&#xff1a; Code: class Solution {public://找到公共子序列的最大长度dp 最小步数串1.size-dp串2.size-dp int minDistance(string word1, string word2) { vector<v…

node 中间件使用例子

NodeJS在中间件领域有着较为广泛的应用&#xff0c;他能做一些中间层事件&#xff0c;把服务端一部分的代码抽出来&#xff0c;减少处理冗余事情付出的代价&#xff0c;同时让服务真正做业务处理而不用关心页面的事情 常见的应用场景有&#xff1a; 跨域&#xff1a;解决跨域问…

二叉树左右树交换

leetcode 226题 翻转二叉树 题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3]…

背就有效!2024下《系统架构设计师》50个高频考点汇总

宝子们&#xff01;上半年软考已经结束一段时间了&#xff0c;准备备考下半年软考高级-系统架构设计师的小伙伴可以开始准备了&#xff0c;毕竟高级科目的难度可是不低的&#xff0c;相信参加过上半年架构的小伙伴深有体会。 这里给大家整理了50个高频考点&#xff0c;涵盖全书…

node更改npm缓存存储位置-并配置环境变量

更改缓存位置 node安装完成之后,在安装目录中新建一个存放缓存的文件夹node_cache 此时这个文件夹必须使用管理员权限才能更改,这使得命令行下使用npm进行下载的时候总是报权限不足的错误:permit 解决办法: 右键 -> 属性 -> 安全 -> 编辑 -> 选择user -> …

【Linux】进程_3

文章目录 五、进程3. 进程4. 进程状态 未完待续 五、进程 3. 进程 在当前&#xff0c;我们只能通过执行可执行程序来让操作系统帮我们启动进程&#xff0c;那我们如何使用代码来自己启动进程呢&#xff1f;我们可以使用 fork() 函数。作用是创建子进程。 我们创建一个程序来…

字节扣子搭建大模型擂台:匿名PK效果,用户当裁判,跑分时代要结束了

字节跳动的扣子&#xff08;coze.cn&#xff09;&#xff0c;给国产大模型们组了个大局—— 在同一个“擂台”上&#xff0c;两个大模型为一组&#xff0c;直接以匿名的方式PK效果&#xff01; 例如我们对两位参赛“选手”同时提问今年高考的题目&#xff1a; 阅读下面的材料&…

探索数字化转型:提升企业客户服务竞争力的策略

当前&#xff0c;数字经济已成为引领经济发展的“主引擎”。在这一背景下&#xff0c;客户服务领域也在发生着深刻变化&#xff0c;传统的以客服热线、人工客服为核心的客户服务模式已不能满足企业发展的需要&#xff0c;而数字化转型成为企业寻求突破的必然选择。 企业可利用大…

OpenAI把GPT-4原始版给了他们:研究不微调只靠提示词能走多远

除了OpenAI自己&#xff0c;居然还有别人能用上GPT-4-Base版&#xff1f;&#xff1f; 也就是未经微调的预训练版&#xff0c;还不会对话聊天&#xff0c;只会补全句子的模型。 EPFL&#xff08;瑞士洛桑联邦理工&#xff09;团队申请到了访问权限&#xff0c;用于研究**“上…

HCIE-QOS流量监管-拥塞管理

QOS流量监管-拥塞管理 QOS数据处理流程流量限速技术-令牌桶技术单桶单速双色标记法双桶单速三色标记法&#xff08;常用&#xff09;双桶双速三色标记法 流量监管承诺访问速率流量监管使用场景配置基于接口的流量监管配置MQC实现流量监管 流量整形流量整形的实现 (1)流量整形的…

LeetCode | 21.合并两个有序链表

这道题也是很经典的一道题了&#xff0c;408的算法题中也考过这个思想&#xff0c;因为两个链表已是升序&#xff0c;合并只需要两个指针&#xff0c;分别指向两个表的表头&#xff0c;分别比较两个指针所指向的结点的val&#xff0c;小的就插入到目标链表里面&#xff0c;再后…

鸿蒙轻内核Kconfig使用笔记

鸿蒙轻内核使用Kconfig进行图形化配置&#xff0c;本文专门讲解下鸿蒙轻内核LiteOS-M和LiteOS-A的图形化配置方法。本文中所涉及的源码&#xff0c;均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_a 、 https://gitee.com/openharmony/kernel_liteos_m 获取。本…

交友系统定制版源码 相亲交友小程序源码全开源可二开 打造独特的社交交友系统

交友系统源码的实现涉及到多个方面&#xff0c;包括前端页面设计、后端逻辑处理、数据库设计以及用户交互等。以下是一个简单的交友系统源码实现的基本框架和关键步骤: 1.数据库设计:用户表:存储用户基本信息&#xff0c;如用户ID、用户名、密码、头像、性别、年龄、地理位置…

【我是产品经理_注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …