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导入、导出excel用法步骤保姆级教程(附封装好的工具类)

《Java导入、导出excel用法步骤保姆级教程(附封装好的工具类)》:本文主要介绍Java导入、导出excel的相关资料,讲解了使用Java和ApachePOI库将数据导出为Excel文件,包括... 目录前言一、引入Apache POI依赖二、用法&步骤2.1 创建Excel的元素2.3 样式和字体2.

Java实现将Markdown转换为纯文本

《Java实现将Markdown转换为纯文本》这篇文章主要为大家详细介绍了两种在Java中实现Markdown转纯文本的主流方法,文中的示例代码讲解详细,大家可以根据需求选择适合的方案... 目录方法一:使用正则表达式(轻量级方案)方法二:使用 Flexmark-Java 库(专业方案)1. 添加依赖(Ma

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

Spring Boot拦截器Interceptor与过滤器Filter详细教程(示例详解)

《SpringBoot拦截器Interceptor与过滤器Filter详细教程(示例详解)》本文详细介绍了SpringBoot中的拦截器(Interceptor)和过滤器(Filter),包括它们的... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)详细教程1. 概述1

Go语言中最便捷的http请求包resty的使用详解

《Go语言中最便捷的http请求包resty的使用详解》go语言虽然自身就有net/http包,但是说实话用起来没那么好用,resty包是go语言中一个非常受欢迎的http请求处理包,下面我们一起来学... 目录安装一、一个简单的get二、带查询参数三、设置请求头、body四、设置表单数据五、处理响应六、超

SpringBoot利用dynamic-datasource-spring-boot-starter解决多数据源问题

《SpringBoot利用dynamic-datasource-spring-boot-starter解决多数据源问题》dynamic-datasource-spring-boot-starter是一... 目录概要整体架构构想操作步骤创建数据源切换数据源后续问题小结概要自己闲暇时间想实现一个多租户平台,

详解如何使用Python提取视频文件中的音频

《详解如何使用Python提取视频文件中的音频》在多媒体处理中,有时我们需要从视频文件中提取音频,本文为大家整理了几种使用Python编程语言提取视频文件中的音频的方法,大家可以根据需要进行选择... 目录引言代码部分方法扩展引言在多媒体处理中,有时我们需要从视频文件中提取音频,以便进一步处理或分析。本文

Java反转字符串的五种方法总结

《Java反转字符串的五种方法总结》:本文主要介绍五种在Java中反转字符串的方法,包括使用StringBuilder的reverse()方法、字符数组、自定义StringBuilder方法、直接... 目录前言方法一:使用StringBuilder的reverse()方法方法二:使用字符数组方法三:使用自

JAVA封装多线程实现的方式及原理

《JAVA封装多线程实现的方式及原理》:本文主要介绍Java中封装多线程的原理和常见方式,通过封装可以简化多线程的使用,提高安全性,并增强代码的可维护性和可扩展性,需要的朋友可以参考下... 目录前言一、封装的目标二、常见的封装方式及原理总结前言在 Java 中,封装多线程的原理主要围绕着将多线程相关的操

SpringIoC与SpringDI详解

《SpringIoC与SpringDI详解》本文介绍了Spring框架中的IoC(控制反转)和DI(依赖注入)概念,以及如何在Spring中使用这些概念来管理对象和依赖关系,感兴趣的朋友一起看看吧... 目录一、IoC与DI1.1 IoC1.2 DI二、IoC与DI的使用三、IoC详解3.1 Bean的存储