深入剖析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核心

  1. 锁状态:StampedLock 使用一个内部变量(通常是一个 long 类型的变量)来维护锁的状态。这个状态不仅表示锁是否被持有,还包含了一个版本号(stamp),用于支持乐观读锁。

  2. 乐观读锁:当线程尝试获取乐观读锁时,StampedLock 会检查当前是否有写锁被持有。如果没有,它会增加一个读锁计数器并返回一个 stamp(通常是当前状态的一个快照)。乐观读锁不会阻塞其他读线程或写线程,但可能在写线程获得锁后读取到不一致的数据。

  3. 悲观读锁:与乐观读锁不同,悲观读锁会阻塞其他写线程的访问。当线程尝试获取悲观读锁时,StampedLock 会检查是否有其他写线程持有锁或正在等待锁。如果没有,它会授予锁并返回一个 stamp。

  4. 写锁:写锁是独占的,意味着同一时间只能有一个线程持有写锁。当线程尝试获取写锁时,StampedLock 会检查是否有其他读锁或写锁被持有。如果有,线程将被阻塞直到锁被释放。

  5. 可重入性:StampedLock 支持锁的可重入性,即一个线程可以多次获得同一个锁而不会导致死锁。这是通过跟踪每个线程的锁持有计数来实现的。

  6. 锁转换: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;}
}

写锁获取

当线程尝试获取写锁时,会调用类似 writeLocktryWriteLock 的方法,这些方法最终会调用一个内部方法来实现锁的获取逻辑。以下是一个简化的示例:

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 变量作为共享资源。我们定义了三个方法:

  1. getBalanceWithOptimisticReadLock:使用乐观读锁尝试读取余额。如果在读取过程中乐观读锁被写锁干扰而失效,它将回退到使用悲观读锁重新读取余额。

  2. getBalanceWithPessimisticReadLock:使用悲观读锁读取余额。这将阻止其他写线程在此期间修改余额,但允许多个读线程同时读取。

  3. updateBalanceWithWriteLock:使用写锁更新余额。这将独占访问共享资源,确保在更新期间没有其他线程能够读取或写入余额。

main 方法中,我们创建了一个 StampedLockExample 实例,并定义了读任务和写任务来模拟多线程环境下的读写操作。然后,我们启动多个线程来执行这些任务。

六、StampedLock与其他锁机制的比较

与传统的ReentrantLock和ReentrantReadWriteLock相比,StampedLock在并发性能上有了显著的提升。这是因为它采用了乐观读锁的策略,允许多个线程同时读取共享资源。
此外,StampedLock还支持可重入锁和公平锁的特性,提供了更灵活的锁控制选项。
然而,StampedLock的使用也相对复杂一些,需要开发者对锁的状态和版本号进行精细的控制和管理。

总结

StampedLock是Java并发库(JUC)中一种高效、灵活的锁机制。它提供了乐观读锁和悲观读写锁的能力,适用于读多写少、数据一致性要求不高的场景。与传统的ReentrantLock和ReentrantReadWriteLock相比,StampedLock在并发性能上有了显著的提升。然而,它的使用也相对复杂一些,需要开发者对锁的状态和版本号进行精细的控制和管理。在实际应用中,开发者应根据具体的场景和需求选择合适的锁机制来确保程序的正确性和性能。



术因分享而日新,每获新知,喜溢心扉。
诚邀关注公众号 码到三十五 ,获取更多技术资料。


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

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

相关文章

服务器数据恢复—光纤环境互斥不当导致存储VMFS卷损坏的数据恢复案例

服务器数据恢复环境&故障: 某公司的信息管理平台,通过3台虚拟机共享了一台存储设备供企业内部使用,存储设备中存放了公司内部重要的数据文件。 由于业务增长的需要,管理员又在这个存储网络上连接了一台Windows server服务器&a…

QT+GDAL实现影像的读取和显示

详细流程参考https://blog.csdn.net/deirjie/article/details/37872743 代码 //open_image.h #pragma once #include <QtWidgets/QMainWindow> #include "ui_open_image.h" #include "gdal_priv.h" #include <QMessageBox> #include <QFi…

专题一——双指针算法

原理&#xff1a;将数组进行区间划分&#xff0c;通过指针(下标)的移动实现题目所要求的区间&#xff08;数组分块&#xff09; &#xff08;实现代码统一是C&#xff09; 建议在做题与看题解时要自己反复模拟这个实现的过程&#xff0c;以后在做题做到类似的题才能举一反三&am…

git基础-记录对仓库的更改

记录对仓库的更改 到目前为止&#xff0c;我们应该在本地计算机上拥有一个真正的 Git 仓库&#xff0c;并且拥有所有文件的一个检出或工作副本。通常&#xff0c;我们会想要开始进行更改&#xff0c;并在项目达到想要记录的状态时&#xff0c;将这些更改的快照提交到我们仓库中…

【数字图像处理系列】读取图像

【数字图像处理系列】读取图像 使用函数 imread 可以将图像读人 MATLAB 环境&#xff0c;imread 的语法为 imread(filename)其中&#xff0c;filename是一个含有图像文件全名的字符串(包括任何可用的扩展名)。例如&#xff0c;命令行 >>f imread(pout.tif)将tif图像po…

【面试经典150 | 数组】分发糖果

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;贪心两次遍历 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容…

慧哥充电桩平台 二轮电动自行车 四轮汽车 云快充1.5 云快充1.6

文章目录 一、产品功能部分截图1.手机端&#xff08;小程序、安卓、ios&#xff09;2.PC端 二、小程序体验账号以及PC后台体验账号1.小程序体验账号2.PC后台体验账号关注公众号获取最新资讯 三、产品简介&#xff1f;1. 充电桩云平台&#xff08;含硬件充电桩&#xff09;&…

Redis相关操作高阶篇--集群搭建

Redis相关操作大全一篇全搞定-CSDN博客 Redis集群 是一个由多个主从节点群组成的分布式服务器群&#xff0c;它具有复制、高可用和分片特性。Redis集群不需要seninel哨兵也能完成节点移除和故障转移的功能。需要将每个节点 设置成集群模式&#xff0c;这种集群模式没有中心节…

vulnhub打靶记录——Mycmsms

文章目录 一、环境布置主机发现 二、端口扫描nikto基本探测目录扫描CMS EXP搜索探查mysql数据库CMS代码审计CMS后台权限提升 一、环境布置 靶机在virtualbox中搭建&#xff0c;攻击机使用vmware中安装的kali&#xff0c;主要是解决kali能ping通靶机&#xff0c;同时能访问外网…

Qt扫盲-D-Bus调试工具

D-Bus调试工具 一、概述二、安装d-feet三、使用d-feet 一、概述 在调试D-Bus时&#xff0c;如果只是在命令行下使用&#xff0c;效率并不是很高&#xff0c;我们可以使用更加直观的GUI工具来查看&#xff0c;我推荐使用 d-feet工具&#xff0c;这个能直观的查看系统总线和会话…

企业异地组网的挑战与解决方案

在当今互联的商业环境中&#xff0c;建立跨越不同地点的网络变得至关重要。本文将探讨企业异地组网的复杂性&#xff0c;并提供实用解决方案以应对这些挑战。通过了解创新技术如SD-WAN和网络安全措施&#xff0c;确保企业异地组网在性能和数据完整性方面的最佳表现。 由于距离和…

用例图画法

介绍 在软件工程中&#xff0c;用例图是一种用于描述系统功能和与之交互的参与者&#xff08;Actors&#xff09;之间关系的图形表示方法。 绘图步骤 确定参与者&#xff08;Actors&#xff09;&#xff1a;识别系统中的各个参与者&#xff0c;这些参与者可以是人、其他系统或外…

unity学习(63)——预制体

1.运行发现预制体初始化的时候存在问题 这里有许多技巧&#xff0c;需要细看。 2.预制体在MapHandler.cs的定义如下 3.把MapHandler绑到相机上&#xff0c;在相机的属性栏中找到赋值部分。 4.size设置成2&#xff0c;然后把模型拖拽到1号索引位置上 5.运行之后预制体确实成功实…

【实例】React 状态管理库 MobX Redux 入门及对比

上一篇&#xff1a;【实例】React 组件传值方法: Props、回调函数、Context、路由传参 MobX MobX 是一个状态管理库&#xff0c;它提供了一种响应式的数据流方案&#xff0c;使得状态的变化能够自动地反映到相关的组件中。 MobX 的核心理念是可观察的状态&#xff08;Observa…

思科无线控制器配置学习

文章目录 简单拓扑WLC配置 简单拓扑 WLC配置 WLC#show running-config Building configuration...Current configuration : 11943 bytes ! ! Last configuration change at 16:22:44 UTC Thu Mar 14 2024 by admin ! version 17.9 service timestamps debug datetime msec se…

鸿蒙一次开发,多端部署(七)响应式布局

自适应布局可以保证窗口尺寸在一定范围内变化时&#xff0c;页面的显示是正常的。但是将窗口尺寸变化较大时&#xff08;如窗口宽度从400vp变化为1000vp&#xff09;&#xff0c;仅仅依靠自适应布局可能出现图片异常放大或页面内容稀疏、留白过多等问题&#xff0c;此时就需要借…

农业四情监测系统的工作原理

农业四情监测系统的工作原理【TH-Q1】农业四情监测系统是一种应用现代科技手段&#xff0c;以实现对农田环境信息的实时监测和数据采集的系统。这一系统通过对农田的土壤、气象、病虫害以及作物生长状况等四个方面的实时监测&#xff0c;帮助农民和农业管理者更好地了解和掌握农…

网络: 传输层

功能: 将数据从发送到传给接收端 UDP 无连接状态: 知道对端的IP和端口号就直接进行传输, 不需要建立连接不可靠: 没有确认机制, 没有重传机制. 出错不会管面向数据包: 不能够灵活的控制读写数据的次数和数量 发送速度快: 立即发送 报文结构 TCP 面向连接可靠 校验和序列号(按…

测试框架到底是什么,如何定义?

测试框架的关键组件是什么&#xff1f; 测试执行引擎&#xff1a;协调测试的运行、管理序列和报告结果。 测试脚本存储库&#xff1a;存储将要执行的实际测试用例或脚本。 测试数据&#xff1a;测试执行所需的输入数据&#xff0c;可以是静态的、动态的或动态生成的。 存根和…

十四届蓝桥杯 冶炼金属(二分 / 公式)

二分代码1&#xff1a; #include<iostream> #include<cstdio> #include<cmath> using namespace std;int get(int a, int b){int l1;r1e91;while(l<r){int mid lr >>1;if(a / mid < b){r mid;}else l mid 1;}return l; } int main() {int n…