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

相关文章

Springboot配置文件相关语法及读取方式详解

《Springboot配置文件相关语法及读取方式详解》本文主要介绍了SpringBoot中的两种配置文件形式,即.properties文件和.yml/.yaml文件,详细讲解了这两种文件的语法和读取方... 目录配置文件的形式语法1、key-value形式2、数组形式读取方式1、通过@value注解2、通过

Java 接口定义变量的示例代码

《Java接口定义变量的示例代码》文章介绍了Java接口中的变量和方法,接口中的变量必须是publicstaticfinal的,用于定义常量,而方法默认是publicabstract的,必须由实现类... 在 Java 中,接口是一种抽象类型,用于定义类必须实现的方法。接口可以包含常量和方法,但不能包含实例

JAVA Calendar设置上个月时,日期不存在或错误提示问题及解决

《JAVACalendar设置上个月时,日期不存在或错误提示问题及解决》在使用Java的Calendar类设置上个月的日期时,如果遇到不存在的日期(如4月31日),默认会自动调整到下个月的相应日期(... 目录Java Calendar设置上个月时,日期不存在或错误提示java进行日期计算时如果出现不存在的

Springboot的配置文件及其优先级说明

《Springboot的配置文件及其优先级说明》文章介绍了SpringBoot的配置文件,包括application.properties和application.yml的使用,以及它们的优先级,还讨... 目录配置文件内置配置文件yml与properties的比较优先级比较外置配置文件springboot

自定义注解SpringBoot防重复提交AOP方法详解

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否... 目录防重复提交流程引入依赖properties配置自定义注解切面Redis工具类controller

Java利用Spire.XLS for Java自动化设置Excel的文档属性

《Java利用Spire.XLSforJava自动化设置Excel的文档属性》一个专业的Excel文件,其文档属性往往能大大提升文件的可管理性和可检索性,下面我们就来看看Java如何使用Spire... 目录Spire.XLS for Java 库介绍与安装Java 设置内置的 Excel 文档属性Java

Java中的CompletableFuture核心用法和常见场景

《Java中的CompletableFuture核心用法和常见场景》CompletableFuture是Java8引入的强大的异步编程工具,支持链式异步编程、组合、异常处理和回调,介绍其核心用法,通过... 目录1、引言2. 基本概念3. 创建 CompletableFuture3.1. 手动创建3.2.

java中4种API参数传递方式统一说明

《java中4种API参数传递方式统一说明》在Java中,我们可以使用不同的方式来传递参数给方法或函数,:本文主要介绍java中4种API参数传递方式的相关资料,文中通过代码介绍的非常详细,需要的... 目录1. 概述2. 参数传递方式分类2.1 Query Parameters(查询参数)2.2 Path

SpringBoot整合 Quartz实现定时推送实战指南

《SpringBoot整合Quartz实现定时推送实战指南》文章介绍了SpringBoot中使用Quartz动态定时任务和任务持久化实现多条不确定结束时间并提前N分钟推送的方案,本文结合实例代码给大... 目录前言一、Quartz 是什么?1、核心定位:解决什么问题?2、Quartz 核心组件二、使用步骤1

Java线程池核心参数原理及使用指南

《Java线程池核心参数原理及使用指南》本文详细介绍了Java线程池的基本概念、核心类、核心参数、工作原理、常见类型以及最佳实践,通过理解每个参数的含义和工作原理,可以更好地配置线程池,提高系统性能,... 目录一、线程池概述1.1 什么是线程池1.2 线程池的优势二、线程池核心类三、ThreadPoolE