【JUC】三十二、邮戳锁StampedLock
2023-12-22 21:01:07
前面提到了从无锁 ? 独占锁 ? 读写锁,但读写锁存在写锁饥饿的情况。
- 📕【读写锁的演化与锁降级】
本篇邮戳锁(也称版本锁、票据锁)即是对读写锁的再一次演化。
1、邮戳锁
StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。
stamp,戳记,long类型,代表了锁的状态,当stamp返回0时,表示线程获取锁失败,且当释放锁或者转换锁时,都要传入最初获取的stamp值。
2、锁饥饿问题的解决思路
idea1:使用公平锁可一定程度上缓解锁饥饿问题,但这样是以牺牲系统吞吐量为代价的
new ReentrantReadWriteLock(true);
idea2:Java8的StampedLock类的乐观读锁
读写锁的实现类ReentrantReadWriteLock是读写互斥,写写互斥,但读读共享,因此性能比synchronized等独占锁好很多。而其读写互斥的特点,在读线程多,写线程少时,会导致写锁饥饿,基于此,邮戳锁提供乐观锁。
获取乐观锁后,其他线程尝试获取写锁时不再会被阻塞,同时,乐观读后也要对结果进行校验。很乐观,认为我读的时候,不会有人改,如此,相比ReentrantReadWriteLock读写锁,性能又上了一个台阶。
对短的只读代码段,使用乐观模式通常可以减少争用并提高吞吐量 (强调短的读,是因为读的时间短了,中间被写线程改数据的概率就低,更容易乐观成功)
3、邮戳锁的特点
- 所有获取锁的方法,都返回一个邮戳Stamp,Stamp为零表示锁获取失败,其余都表示成功
- 所有释放锁的方法,都需要一个邮戳Stamp,这个Stamp必须是和成功获取锁时得到的stamp一致
- StampedLock是不可重入的,因此,如果一个线程已经持有了写锁,又再去获取写锁的话就会造成死锁
StampedLock有三种访问模式:
- Reading(读模式悲观):功能和ReentrantReadWriteLock的读锁类似
- Writing(写模式):功能和ReentrantRerdWriteLock的写锁类似
- Optimistic reading (乐观读模式):无锁机制,类似于数据库中的乐观锁,
支持读写并发
,很乐观的认为读取时没人修改,假如被修改,再升级为悲观读模式
4、代码演示:邮戳锁的传统读写用法
以下演示,邮戳锁也可以当作传统的读写锁来使用:
//资源类
public class ShareSource {
int number = 6;
StampedLock stampedLock = new StampedLock();
/**
* 写
*/
public void writer(){
long stamp = stampedLock.writeLock();
System.out.println(Thread.currentThread().getName() + "写线程准备修改共享资源");
try{
number = number + 1;
}finally {
stampedLock.unlockWrite(stamp);
}
System.out.println(Thread.currentThread().getName() + "写线程修改结束");
}
/**
* 悲观读,没读完时,写锁无法获取
* 读的过程中停几秒,以明显看到是否允许写锁进入
*/
public void read(){
long stamp = stampedLock.readLock();
System.out.println(Thread.currentThread().getName() + " 进入读锁,预计4秒后读取完成");
for (int i = 1; i < 5; i++) {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace();}
NumberFormat nf = NumberFormat.getPercentInstance();
nf.setMaximumFractionDigits(2); //最大小数位数
System.out.println(Thread.currentThread().getName() + "读取进度" + nf.format(i/4.00));
}
try{
int result = number;
System.out.println(Thread.currentThread().getName() + "读取完成,获得共享对象变量值为:" + result);
}finally {
stampedLock.unlockRead(stamp);
}
}
}
测试类:
public class Test {
public static void main(String[] args) throws InterruptedException {
ShareSource shareSource = new ShareSource();
new Thread(() -> {
shareSource.read();
}, "readThread").start();
//为了测试效果,确保读线程先启动
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> {
shareSource.writer();
}, "writeThread").start();
//这里不用JUC辅助类了,直接sleep等着
TimeUnit.SECONDS.sleep(6);
System.out.println(Thread.currentThread().getName() + "查看最终结果number: " + shareSource.number);
}
}
运行,和普通的读写锁没什么区别,没读完前,写线程不让进,依旧读写互斥:
5、代码演示:邮戳锁之乐观读
下面用邮戳锁的乐观读,演示读的过程也允许写锁介入。先看乐观读的校验方法:
//返回true,代表中途没被其他线程修改
public boolean validate(long stamp)
Demo修改,写资源类的乐观读的业务方法:
//资源类
/**
* 乐观读
* 读的过程中允许写锁的获取
*/
public void optimisticRead(){
long stamp = stampedLock.tryOptimisticRead();
int result = number;
//故意间隔4秒,看中间有没其他线程修改过number
for (int i = 1; i < 5; i++) {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName() + "正在读取中,"
+ i + "秒后,validate方法值(true无修改,false有修改):" + stampedLock.validate(stamp));
}
//读完了,校验下中途有没被写线程修改过,若有,则升级为悲观读,重读,若无,则无锁偷鸡成功
if (!stampedLock.validate(stamp)){
System.out.println("中途被写线程修改过!!!");
stamp = stampedLock.readLock();
try {
System.out.println("已从乐观读升级为悲观读.......");
result = number;
System.out.println("悲观读重读的结果:" + result);
} finally {
stampedLock.unlockRead(stamp);
}
}
//最终结果
System.out.println("final result: " + result);
}
测试类同上,启动两个线程充当读线程和写线程:
再调,6妙后,写线程介入,发现乐观读偷鸡成功:
6、邮戳锁的缺点
- StampedLock 不支持重入,没有Re开头
- StampedLock 的悲观读锁和写锁都不支持条件变量 (Condition)
- 使用stampedLock一定不要调用中断操作,即不要调用interrupt()方法
7、终章回顾
到此,JUC课程整理完成。2023/12/21 10:54
画个xmind图整体梳理一遍:
顺畅多了,舒服了。之前课完结,最后一篇笔记整理完直接走人,这次按课程老师说的串一遍,不错的习惯。
文章来源:https://blog.csdn.net/llg___/article/details/135116567
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!