java多线程:详解StampedLock的特性,悲观读和乐观读,排他写

2023-12-10 16:50

本文主要是介绍java多线程:详解StampedLock的特性,悲观读和乐观读,排他写,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.JDK1.8以前有那么多锁了,为什么还要StampedLock

  • 一般应用,都是读多写少,ReentrantReadWriteLock 因读写互斥,故读时阻塞写,因而性能上上不去。可能会使写线程饥饿,StampedLock营运而生。

2.StampedLock的特点

  • 所有获取锁的方法,都返回一个邮戳(Stamp ), Stamp 0 表示获取失败,其余都表示成功; 所有释放锁的方法,都要一个邮戳(Stamp ),这个 Stamp 必须是和成功获取锁时得到的 Stamp一致;StampedLock 是不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁) 支持锁升级跟锁降级 , 可以乐观读也可以悲观读 , 使用有限次自旋,增加锁获得的几率,避免上下文切换带来的开销 , 乐观读不阻塞写操作,悲观读,阻塞写的操作。

3.StampedLock的优点

  • 相比于ReentrantReadWriteLock,吞吐量大幅提升

4.StampedLock的缺点

  • api相对复杂,容易用错 内部实现相比于ReentrantReadWriteLock复杂得多

5.StampedLock的原理

  • 每次获取锁的时候,都会返回一个邮戳(stamp),相当于mysql里的version字段,释放锁的时候,再根据之前的获得的邮戳,去进行锁释放

6.使用stampedLock注意点

  • 如果使用乐观读,一定要判断返回的邮戳是否是一开始获得到的,如果不是,要去获取悲观读锁,再次去读取

7.多线程模拟读写锁,使用StampedLock的readLock()和writeLock()方法

public class StampedLockDemo {private int i = 0;private int j = 0;private static final StampedLock stampedLock = new StampedLock();public void readMethod() {// 获取读锁long readLock = stampedLock.readLock ();try {System.out.println (String.format ("线程%s获得i======》%d,获得j======》%d", Thread.currentThread ().getName (), i, j));} finally {// 释放读锁stampedLock.unlockRead (readLock);}}public void writeMethod() {// 获取写锁long writeLock = stampedLock.writeLock ();try {i++;j++;System.out.println (String.format ("线程%s正在写入i====>%d,j=====>%d", Thread.currentThread ().getName (), i, j));} finally {// 释放写锁stampedLock.unlockWrite (writeLock);}}public static void main(String[] args) {StampedLockDemo stampedLockDemo = new StampedLockDemo ();for (int i = 0; i < 10; i++) {new Thread (() -> {stampedLockDemo.writeMethod ();}).start ();}for (int j = 0; j < 3; j++) {new Thread (() -> {stampedLockDemo.readMethod ();}).start ();}}}
  • 控制台输出:

  • 未使用读写锁的情况

PS:与期待的值不一样,我们需要写入完成再去读取。

8.下面是JDK1.8源码自带的示例:

public class StampedLockDemo {//一个点的x,y坐标private double x,y;private final StampedLock sl = new StampedLock();//【写锁(排它锁)】void move(double deltaX,double deltaY) {// an exclusively locked method /**stampedLock调用writeLock和unlockWrite时候都会导致stampedLock的stamp值的变化* 即每次+1,直到加到最大值,然后从0重新开始 **/long stamp =sl.writeLock(); //写锁try {x +=deltaX;y +=deltaY;} finally {sl.unlockWrite(stamp);//释放写锁}}//【乐观读锁】double distanceFromOrigin() { // A read-only method/*** tryOptimisticRead是一个乐观的读,使用这种锁的读不阻塞写* 每次读的时候得到一个当前的stamp值(类似时间戳的作用)*/long stamp = sl.tryOptimisticRead();//这里就是读操作,读取x和y,因为读取x时,y可能被写了新的值,所以下面需要判断double currentX = x, currentY = y;/**如果读取的时候发生了写,则stampedLock的stamp属性值会变化,此时需要重读,* 再重读的时候需要加读锁(并且重读时使用的应当是悲观的读锁,即阻塞写的读锁)* 当然重读的时候还可以使用tryOptimisticRead,此时需要结合循环了,即类似CAS方式* 读锁又重新返回一个stampe值*/if (!sl.validate(stamp)) {//如果验证失败(读之前已发生写)stamp = sl.readLock(); //悲观读锁try {currentX = x;currentY = y;}finally{sl.unlockRead(stamp);//释放读锁}}//读锁验证成功后执行计算,即读的时候没有发生写return Math.sqrt(currentX *currentX + currentY *currentY);}//【悲观读锁】void moveIfAtOrigin(double newX, double newY) { // upgrade// 读锁(这里可用乐观锁替代)long stamp = sl.readLock();try {//循环,检查当前状态是否符合while (x == 0.0 && y == 0.0) {/*** 转换当前读戳为写戳,即上写锁* 1.写锁戳,直接返回写锁戳* 2.读锁戳且写锁可获得,则释放读锁,返回写锁戳* 3.乐观读戳,当立即可用时返回写锁戳* 4.其他情况返回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);}}
}

9.特征

  • StampedLock支持三种模式: 写锁、悲观读和乐观读。
  • 允许多 个线程同时获取乐观锁和悲观读锁。
  • 只允许一个线程获取写锁,写锁和悲观读锁是互斥的。
  • 使用StampedLock一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()。
  • StampedLock里的写锁和悲观读锁加锁成功之后,都会返回一个stamp;然后解锁的时候,需要传入这个 stamp。
  • StampedLock不支持重入(ReadWriteLock支持)
  • StampedLock的悲观读锁、写锁都不支持条件变量。
  • StampedLock支持锁的降级(通过tryConvertToReadLock()方法实现)和升级(通过 tryConvertToWriteLock()方法实现),但是建议你要慎重使用。

链接:java多线程:详解使用ReentrantReadWriteLock读写锁,以及锁降级

 

这篇关于java多线程:详解StampedLock的特性,悲观读和乐观读,排他写的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/477699

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima