ReentrantReadWriteLock、StampedLock说明
目录
1.关于锁的大厂面试题
2.ReentrantReadWriteLock(读写锁)
3.StampedLock(邮戳锁)
3.1是什么
3.2它是由锁饥饿问题引出的
3.3StampedLock的特点
3.4乐观模式的代码演示
3.5Stamped的缺点
0.本章路线总纲
无锁→独占锁→读写锁→邮戳锁
无锁状态就是常规场景,独占锁就是使用synchronized和ReentrantLock实现的锁,所以不多做说明。
1.关于锁的大厂面试题
你知道Java里面有哪些锁?
你说你用过读写锁,锁饥饿问题是什么?
有没有比读写锁更快的锁?
StampedLock知道吗?(邮戳锁/票据锁)
ReentrantReadVriteLock有锁降级机制,你知道吗?
。。。
2.ReentrantReadWriteLock(读写锁)
读写锁定义为:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。
意义和特点
它只允许读读共存,而读写和写写依然是互斥的,大多实际场景是“读/读”线程间并不存在互斥关系,只有"读/写"线程或"写/写"线程间的操作需要互斥的。因此引入ReentrantReadWriteLock。
一个ReentrantReadWriteLock同时只能存在一个写锁但是可以存在多个读锁,但不能同时存在写锁和读锁。也即一个资源可以被多个读操作访问 或 一个写操作访问,但两者不能同时进行。
只有在读多写少情景之下,读写锁才具有较高的性能体现。
特点,可重入,读写兼顾。
代码演示
public class ReentrantReadWriteLockDemo {HashMap<String,String> map=new HashMap<>();// 独占锁ReentrantLock lock=new ReentrantLock();// 读写锁 读写互斥,读读共享ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();public void write(String key,String value){try {
// lock.lock();rwLock.writeLock().lock();System.out.println(Thread.currentThread().getName()+"\t 开始写入");map.put(key,value);try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t 完成写入");}finally {
// lock.unlock();rwLock.writeLock().unlock();}}public void read(String key){try {
// lock.lock();rwLock.readLock().lock();System.out.println(Thread.currentThread().getName()+"\t 开始读取");String result = map.get(key);try {TimeUnit.MILLISECONDS.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t 完成读取,结果:"+result);}finally {
// lock.unlock();rwLock.readLock().unlock();}}public static void main(String[] args) {ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();for (int i = 0; i < 10; i++) {int finalI=i;new Thread(()->{demo.write(finalI+"",finalI+"");},String.valueOf(i)).start();}for (int i = 0; i < 10; i++) {int finalI=i;new Thread(()->{demo.read(finalI+"");},String.valueOf(i)).start();}//演示读锁没有完成之前,写锁无法获取for (int i = 0; i < 10; i++) {int finalI=i;new Thread(()->{demo.write(finalI+"",finalI+"");},"新写锁"+String.valueOf(i)).start();}}
}
写操作一次只能开始一个,其他读和写都不能操作;读操作可以同时开始多个,写线程不能操作。
从写锁→读锁,ReentrantReadWriteLock可以降级
锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性。
代码演示
/*** 锁降级:遵循获取写锁-->再获取读锁-->再释放写锁的次序, 写锁能够降级成为读锁。* 如果一个线程占有了写锁,在不释放写锁的宿况下, 它还能占有读锁,即写锁降级为读锁。* 读没有完成时候写锁无法获得锁,必须要等待读锁读完后才有机会写*/
public class LockDownGradingDemo {public static void main(String[] args) {ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();//同一个线程中 ,写锁可以降级为读锁writeLock.lock();System.out.println("写入");readLock.lock();System.out.println("读取");readLock.lock();writeLock.lock();System.out.println("-------------------------------");//当上下两部分代码同时执行,读锁可以升级为写锁,(理论来说读锁无法升级为写锁,可能跟底层实现的细节有关)//同一个线程中 ,读锁无法升级为写锁readLock.lock();System.out.println("读取");writeLock.lock();System.out.println("写入");writeLock.lock();readLock.lock();}
}
如果有线程在读,那么写线程是无法获取写锁的,是悲观锁的策略。
不可锁升级
写锁和读锁是互斥的
源码总结
1 代码中声明了一个volatile类型的cacheValid变量,保证其可见性。
2 首先获取读锁,如果cache不可用,则释放读锁。获取写锁,在更改数据之前,再检查一次cacheValid的值,然后修改数据,将cacheValid置为true,然后在释放写锁前立刻抢夺获取读锁;此时,cache中数据可用,处理cache中数据,最后释放读锁。这个过程就是一个完整的锁降级的过程,目的是保证数据可见性。
总结:一句话,同一个线程自己持有写锁时再去拿读锁,其本质相当于重入。
如果违背锁降级的步骤,如果违背锁降级的步骤,如果违背锁降级的步骤 如果当前的线程C在修改完cache中的数据后,没有获取读锁而是直接释放了写锁,那么假设此时另一个线程D获取了写锁并修改了数据,那么c线程无法感知到数据已被修改,则数据出现错误。
如果遵循锁降级的步骤
线程C在释放写锁之前获取读锁,那么线程D在获取写锁时将被阻塞,直到线程C完成数据处理过后,释放读锁。这样可以保证返回的数据是这次更新的数据,该机制是专门为了缓存设计的。
3.StampedLock(邮戳锁)
3.1是什么
StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。
邮戳锁也叫票据锁。
stamp(戳记,long类型)代表了锁的状态。当stamp返回零时,表示线程获取锁失败。
并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值。
3.2它是由锁饥饿问题引出的
锁饥饿问题
ReentrantReadWriteLock实现了读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,假如当前1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那1个写线程就悲剧了。
因为当前有可能会一直存在读锁,而无法获得写锁,根本没机会写。
如何缓解?
1)使用“公平“策略可以一定程度上缓解这个问题,但是“公平“策略是以栖性系统吞吐量为代价的。
2)StampedLock类的乐观读锁
ReentrantReadWriteLock
允许多个线程同时读,但是只允许一个线程写,在线程获取到写锁的时候,其他写操作和读操作都会处于阻塞状态,读锁和写锁也是互斥的,所以在读的时候是不允许写的,读写锁比传统的synchronized速度要快很多,原因就是在于ReentrantReadWriteLock支持读并发,读读可以共享
StampedLock横空出世
ReentrantReadWriteLock的读锁被占用的时候,其他线程尝试获取写锁的时候会被阻塞。
但是,StampedLock采取乐观获取锁后,其他线程尝试获取写锁时不会被阻塞,这其实是对读锁的优化,所以,在获取乐观读锁后,还需要对结果进行校验。
对短的只读代码段,使用乐观模式通常可以减少争用并提高吞吐量。
3.3StampedLock的特点
所有获取锁的方法,都返回一个邮戳(Stamp), stamp为零表示获取失败,其余都表示成功;
所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
StampedLock有三种访问模式
①Reading(读模式悲观):功能和ReentrantReadWriteLock的读锁类似
②Writing (写模式):功能和ReentrantRedWriteLock的写锁类似
③Optimistic readina(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认对读取时没人修改,假如被修改再实现升级为悲观读模式
3.4乐观模式的代码演示
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;/*** @Description: StampedLock = ReentrantReadWriteLock + 读的过程中也允许获取写锁介入*/
public class StampedLockDemo {StampedLock stampedLock=new StampedLock();int number=37;public void write(){long stamp = stampedLock.writeLock();System.out.println(Thread.currentThread().getName()+"\t 开始写");try {number+=13;System.out.println(Thread.currentThread().getName()+"\t 结束写");}finally {stampedLock.unlockWrite(stamp);}}//悲观读,都没有完成时候写锁无法获取锁public void read(){long stamp = stampedLock.readLock();System.out.println(Thread.currentThread().getName()+"\t 开始读");for (int i = 0; i < 4; i++) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t 读取中.....");}try {System.out.println(Thread.currentThread().getName()+"\t 结束读,结果为:"+number);System.out.println("写线程没有修改成功,读锁时候写锁无法介入,传统的读写互斥");}finally {stampedLock.unlockRead(stamp);}}// 乐观读,读的过程中也允许获取写锁介入public void tryOptimisticRead(){long stamp = stampedLock.tryOptimisticRead();//暂停几秒,很乐观认为读取中没有其它线程修改过number值,具体考判断System.out.println("4秒前stampedLock.validate方法值(true无修改,false有修改)\t "+stampedLock.validate(stamp));for (int i = 0; i < 4; i++) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"\t 读取中.....\t"+"stampedLock.validate方法值(true无修改,false有修改)\t "+stampedLock.validate(stamp));}if (stampedLock.validate(stamp)){System.out.println(Thread.currentThread().getName()+"\t finally value:"+number);}else {System.out.println("数据修改过");stamp = stampedLock.readLock();try {System.out.println("从乐观读 升级为 悲观读");System.out.println("悲观读后结果为:"+number);}finally {stampedLock.unlockRead(stamp);}}}public static void main(String[] args) {StampedLockDemo stampedLockDemo=new StampedLockDemo();//乐观读new Thread(()->{stampedLockDemo.tryOptimisticRead();},"raedThread").start();//悲观读
// new Thread(()->{
// stampedLockDemo.read();
// },"raedThread").start();new Thread(()->{stampedLockDemo.write();},"writeThread").start();}
}
3.5Stamped的缺点
StampedLock 不支持重入,没有Re开头。
StampedLock的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。
使用StampedLock一定不要调用中断操作,即不要调用interrupt()方法。
ReentrantReadWriteLock、StampedLock说明相关推荐
- 并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition
文章目录 J.U.C脑图 ReentrantLock概述 ReentrantLock 常用方法 synchronized 和 ReentrantLock的比较 ReentrantLock示例 读写锁R ...
- Java锁详解之改进读写锁StampedLock
文章目录 先了解一下ReentrantReadWriteLock StampedLock重要方法 StampedLock示例 StampedLock可能出现的性能问题 StampedLock原理 St ...
- java中Locks的使用
文章目录 Lock和Synchronized Block的区别 Lock interface ReentrantLock ReentrantReadWriteLock StampedLock Cond ...
- JUC--005--locks1
JDK 1.8.0_152 java.util.concurrent.locks 包下有:AbstractOwnableSynchronizer AbstractQueuedLongSynchroni ...
- 明翰Java教学系列之多线程篇V0.2(持续更新)
文章目录 传送门 前言 背景知识 并行与并发 线程与进程 内存模型 1. 计算机内存模型 `2. Java内存模型` 2.1 内存交互 2.1.1 交互操作 2.1.2 交互规则 `2.2 并发编程特 ...
- ReentrantReadWriteLock、StampedLock读写锁
1.ReentrantReadWriteLock 当读取操作远远高于写操作时,这时候使用读写锁让读-读可以并发(即然多个线程的读取是可以并行的),提高性能. 类似于数据库中的 select ... f ...
- ReentrantReadWriteLock、StampedLock
ReentrantLock.ReentrantReadWriteLock.StampedLock 读写锁 一个资源可以被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程. 小口诀:读写互 ...
- Java 独占锁ReentrantLock、读(悲观读)写锁ReentrantReadWriteLock、读(乐观读/悲观读)写锁StampedLock
1.AbstractQueuedSynchronizer 锁必然要知道AbstractQueuedSynchronizer(AQS),AQS提供了一个框架,用于实现依赖于先进先出(FIFO)等待队列的 ...
- JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快!
JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快! 一.ReentrantReadWriteLock(读写锁) 1.读写锁存在 ...
最新文章
- 输入vue ui没反应
- (转载)macOS 解决apue.h不存在的问题
- 【风控术语】数字金融反欺诈技术名词表
- OpenCV图像腐蚀函数erode()的使用
- 【转】关于使用Android6.0编译程序时,出现getSlotFromBufferLocked: unknown buffer: 0xac0f8650问题的解释...
- 再论EM算法的收敛性和K-Means的收敛性
- php自定义商品属性,php – 可变产品属性:自定义每个显示的单选按钮文本值
- TypeScript - Interfaces
- Netty高可靠性设计:优化建议
- 计算机网络复习-运输层
- 因打印日志而引发的故障
- Perl中的执行上下文
- 【2015沈阳区域赛F=HDU5514】Frogs(圆上n个青蛙跳统计跳劲哪些点---欧拉函数求和+思维)
- 出租车GPS数据处理
- cad卸载_CAD卸载不干净,如何清理CAD注册表
- wps如何只让他显示3级标题_怎么设置一二三级标题
- 超级简单图解, 轻松设置三级域名泛解析,免hosts设置访问web项目
- 【热点资讯】哪所英国大学最适合你?
- Linux内核启动过程和Bootloader
- 搭配Online:腾讯吃鸡手游《PUBG Mobile》及《和平精英》(前《刺激战场》)全球收入超15亿美元!
热门文章
- AM335x启动流程(BootRom-MLO-Uboot)超详细源码分析
- Unity-ShaderGraph学习笔记第一步: 如何打开ShaderGraph 制作全息效果Shader
- 计算机中怎样重新安装ps,【2人回答】电脑要重装系统,不想重装Photoshop CS6,怎么办?-3D溜溜网...
- 要求: 编写一个程序,提示用户输入: 姓名 ,身份证号(需要一次输完,中间用逗号分隔)随后打印用户的星座。 提示:输入功能用C实现会很简单,其他语言的话,就不用输入姓名和逗号了,直接输入身份证号就
- CCF:201712-4 行车路线
- 笔记本硬盘读取测试软件,电脑硬盘怎样检测 电脑硬盘故障检测软件【详解】...
- 360全景拼接 opencv_opencv实现的全景图种类与步骤
- 产品运营 | 浅谈漫画平台的数据与智能
- 前端开发工具HbuilderX的使用技巧
- 【项目源码】JSP超市积分管理系统源码下载