深入剖析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在并发性能上有了显著的提升。然而,它的使用也相对复杂一些,需要开发者对锁的状态和版本号进行精细的控制和管理。在实际应用中,开发者应根据具体的场景和需求选择合适的锁机制来确保程序的正确性和性能。



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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

谷歌浏览器调用相同url数据不刷新

原代码 原因 谷歌浏览访问相同接口默认调用缓存数据 解决方案 添加时间戳

机器视觉学习(六)—— 图像的颜色识别

目录 一、色彩空间 1.1 RGB色彩空间 1.2 HSV色彩空间 1.3 灰度 1.4 CMYK色彩空间 1.5 Lab色彩空间 二、色彩空间转换 三、识别颜色 3.1 识别一种特定的颜色 3.2 识别多种颜色 一、色彩空间 计算机视觉中常用的色彩空间有RGB色彩空间、HSV色彩空间、CMYK色彩空间、La…

TR1 - Transformer起源与发展

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 | 接辅导、项目定制 1. Transformer的起源与发展 2017年Google在《Attention Is All You Need》中提出了Transformer结构用于序列标注,在翻译任务…

simulink里枚举量的使用--在m文件中创建枚举量实践操作(推荐)

本文将介绍一种非常重要的概念,枚举量,以及它在simulink状态机中的使用,并且给出模型,方便大家学习。 枚举量:实际上是用一个名字表示了一个变量,能够比较方便的表示标志信息 A.简单举例: 1&a…

“低代码+平台”:驱动企业数字化转型与创新的新引擎

“低代码平台”作为一种新兴的软件开发范式,正逐渐成为企业快速响应市场变化、优化业务流程、提升数字化水平的重要手段。它的价值在于,将传统软件开发的复杂性大大降低,赋予了非技术人员或轻量级开发者快速构建应用的能力,并能灵…

docker 哲学 - 网络桥接器、容器网络接口 、容器间的通信方式

1、解释 docker0 veth eth 2、vethXX 和 ethXX 是肯定一一对应吗 比如 eth1 对应 veth1 3、如果 A容器使用 默认创建方式 。定义他内部网络为 eth0,容器B使用 --network 连上 已创建的网络 172.89.2.1 。此时假设 B的 ip是 172.89.2.2 ,容器网络接口是 e…

【蓝桥杯嵌入式】四、各种外设驱动(十一)ADC(1):软件触发与定时器触发

温馨提示:本文不会重复之前提到的内容,如需查看,请参考附录 【蓝桥杯嵌入式】附录 目录 重点提炼: 一、需求分析 1、需要的外设资源分析: 2、外设具体分析: 比赛时ADC可能需要配置的部分:…

视频批量爬虫下载工具|可导出视频分享链接|抖音视频提取软件

便捷的视频批量爬虫软件操作指南 抖音视频下载界面图解 主要功能: 关键词批量提取视频和单独视频提取,提取后下载功能。 功能解析: 1. 关键词批量采集视频的解析 对特定关键词进行搜索和视频提取,例如输入“汽车配件”&#x…

JVM面试篇

面试篇就是复习前面学的 什么是JVM 1.定义:JVM指的是Java虚拟机,本质是一个运行在计算机上的程序 2.作用:为了支持Java中Write Once ,Run Anywhere 编写一次 到处运行的跨平台特性 功能: 1.解释和运行 2.内存管理…

XSKY 智能存储,助力“数据要素 X”先进制造

3 月 21-22 日,主题为“突破 智行”的 IMC2024 第七届中国智造数字科技峰会在重庆召开。作为在先进制造领域拥有领先存储解决方案以及众多应用实践的企业,星辰天合受邀参加了此次峰会并荣获大会颁发的“最佳存储解决方案奖”。同时,星辰天合先…

Django日志(三)

内置TimedRotatingFileHandler 按时间自动切分的log文件,文件后缀 %Y-%m-%d_%H-%M-%S , 初始化参数: 注意 发送邮件的邮箱,开启SMTP服务 filename when=h 时间间隔类型,不区分大小写 S:秒 M:分钟 H:小时 D:天 W0-W6:星期几(0 = 星期一) midnight:如果atTime未指定,…

Swift知识点(二)

17. 字面量协议、模式匹配、条件编译 字面量(Literal) var age 10 var isRed false var name "Jack"上面代码中:10、false、"Jack"就是字面量 可以看到,初始化过程很简单,直接赋值即可 Swif…

Java微服务分布式事务框架seata的TCC模式

🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 往期热门专栏回顾 专栏…

蓝桥杯 EDA 组 2023模拟+真题原理图解析

本文解析了标题内的原理图蓝桥杯EDA组真题,2021-2022 省赛真题/模拟题在上一篇文中。本文中重复或者是简单的电路节约篇幅不在赘述。 其中需要补充和计算原理图的题目解析都放在最下面 一、2023 年第十四届省赛模拟题1 1.1 Type-C 接口电路 通过 CH340N 将数据转化为…

List系列集合:ArrayList、LinkedList --java学习笔记

List系列集合 特点:有序、可重复、有索引 ArrayList:有序、可重复、有索引LinkedList:有序、可重复、有索引 List集合的特有方法 List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的…

Visual Studio 插件 AnAPI++ for VS 2022

Anmial API abbreviation AnAPIis an automatically generated WebAPI project that has encapsulated Jwt Oauth2 token authentication, SqlSugar, Swagger, Nlog, Cross domain technologies, and supports Net6 and above versions Anmial API缩写AnAPI是一个自动生成的Web…

pytest简介以及命令行执行

pytest简介以及安装 pytest简介导入第三方库修改工具类 pytest命令方式执行函数执行pytest中的参数详解 pytest简介 pytest有很多强大的插件 pytest-html (生成html格式的自动化测试报告) pytest-xdist (测试用例分布式执行,多cpu…

如何在 Odoo 17 中创建进度条

Odoo 提供各种字段小部件,例如单选按钮、浮点数、百分比、颜色选择器、复选框、状态栏和 URL。通过使用不同的渲染模板,我们可以使用小部件修改视图。它还帮助我们根据自己的需求进行设计,从而简化、加速、扩展和提高开发效率。在本博客中&am…

三分钟教会你水果音乐编曲软件 FL Studio v21.2.3 中文免费版安装方法

随着数字音乐制作的发展,音乐编曲软件已经成为音乐制作人和爱好者不可或缺的工具。FL Studio v21.2.3是一款功能强大的水果音乐编曲软件,它具有直观的界面,易于学习和使用。本文将介绍FL Studio v21.2.3的特点和优势,以及它在音乐…

cloudflare 应用例子几则

Cloudflare(以下简称 CF) 堪称互联网活菩萨,造就一大批高质量的互联网基础技术服务,更难得的是,几乎所有的服务都有对应的免费的套餐,而且出手大方,基本上有足够的容量或请求次数。 CDN CF 最…