目录

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说明相关推荐

  1. 并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition

    文章目录 J.U.C脑图 ReentrantLock概述 ReentrantLock 常用方法 synchronized 和 ReentrantLock的比较 ReentrantLock示例 读写锁R ...

  2. Java锁详解之改进读写锁StampedLock

    文章目录 先了解一下ReentrantReadWriteLock StampedLock重要方法 StampedLock示例 StampedLock可能出现的性能问题 StampedLock原理 St ...

  3. java中Locks的使用

    文章目录 Lock和Synchronized Block的区别 Lock interface ReentrantLock ReentrantReadWriteLock StampedLock Cond ...

  4. JUC--005--locks1

    JDK 1.8.0_152 java.util.concurrent.locks 包下有:AbstractOwnableSynchronizer AbstractQueuedLongSynchroni ...

  5. 明翰Java教学系列之多线程篇V0.2(持续更新)

    文章目录 传送门 前言 背景知识 并行与并发 线程与进程 内存模型 1. 计算机内存模型 `2. Java内存模型` 2.1 内存交互 2.1.1 交互操作 2.1.2 交互规则 `2.2 并发编程特 ...

  6. ReentrantReadWriteLock、StampedLock读写锁

    1.ReentrantReadWriteLock 当读取操作远远高于写操作时,这时候使用读写锁让读-读可以并发(即然多个线程的读取是可以并行的),提高性能. 类似于数据库中的 select ... f ...

  7. ReentrantReadWriteLock、StampedLock

    ReentrantLock.ReentrantReadWriteLock.StampedLock 读写锁 一个资源可以被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程. 小口诀:读写互 ...

  8. Java 独占锁ReentrantLock、读(悲观读)写锁ReentrantReadWriteLock、读(乐观读/悲观读)写锁StampedLock

    1.AbstractQueuedSynchronizer 锁必然要知道AbstractQueuedSynchronizer(AQS),AQS提供了一个框架,用于实现依赖于先进先出(FIFO)等待队列的 ...

  9. JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快!

    JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快! 一.ReentrantReadWriteLock(读写锁) 1.读写锁存在 ...

最新文章

  1. 输入vue ui没反应
  2. (转载)macOS 解决apue.h不存在的问题
  3. 【风控术语】数字金融反欺诈技术名词表
  4. OpenCV图像腐蚀函数erode()的使用
  5. 【转】关于使用Android6.0编译程序时,出现getSlotFromBufferLocked: unknown buffer: 0xac0f8650问题的解释...
  6. 再论EM算法的收敛性和K-Means的收敛性
  7. php自定义商品属性,php – 可变产品属性:自定义每个显示的单选按钮文本值
  8. TypeScript - Interfaces
  9. Netty高可靠性设计:优化建议
  10. 计算机网络复习-运输层
  11. 因打印日志而引发的故障
  12. Perl中的执行上下文
  13. 【2015沈阳区域赛F=HDU5514】Frogs(圆上n个青蛙跳统计跳劲哪些点---欧拉函数求和+思维)
  14. 出租车GPS数据处理
  15. cad卸载_CAD卸载不干净,如何清理CAD注册表
  16. wps如何只让他显示3级标题_怎么设置一二三级标题
  17. 超级简单图解, 轻松设置三级域名泛解析,免hosts设置访问web项目
  18. 【热点资讯】哪所英国大学最适合你?
  19. Linux内核启动过程和Bootloader
  20. 搭配Online:腾讯吃鸡手游《PUBG Mobile》及《和平精英》(前《刺激战场》)全球收入超15亿美元!

热门文章

  1. AM335x启动流程(BootRom-MLO-Uboot)超详细源码分析
  2. Unity-ShaderGraph学习笔记第一步: 如何打开ShaderGraph 制作全息效果Shader
  3. 计算机中怎样重新安装ps,【2人回答】电脑要重装系统,不想重装Photoshop CS6,怎么办?-3D溜溜网...
  4. 要求: 编写一个程序,提示用户输入: 姓名 ,身份证号(需要一次输完,中间用逗号分隔)随后打印用户的星座。 提示:输入功能用C实现会很简单,其他语言的话,就不用输入姓名和逗号了,直接输入身份证号就
  5. CCF:201712-4 行车路线
  6. 笔记本硬盘读取测试软件,电脑硬盘怎样检测 电脑硬盘故障检测软件【详解】...
  7. 360全景拼接 opencv_opencv实现的全景图种类与步骤
  8. 产品运营 | 浅谈漫画平台的数据与智能
  9. 前端开发工具HbuilderX的使用技巧
  10. 【项目源码】JSP超市积分管理系统源码下载