concrrent类下ReentrantReadWriteLock类的原理以及使用
1、ReentrantreadWriteLock 类的介绍
Lock接口下的子类存在 ReentrantLock子类,该子类是一个线程同步处理类;ReentrantLock类的介绍详见XXX;
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。
ReentrantReadWriteLock类是ReentrantLock的子类;是 ReadWriteLock 接口的实现类;是一个于多线程读写相关的处理类;其具体细分为读锁和写锁;
锁降级:从写锁变成读锁;
锁升级:从读锁变成写锁。
读锁是可以被多线程共享的,写锁是单线程独占的。也就是说写锁的并发限制比读锁高,这可能就是升级/降级名称的来源。
如下代码会产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。
ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.readLock().lock();
System.out.println("get readLock.");
rtLock.writeLock().lock();
System.out.println("blocking");
ReentrantReadWriteLock支持锁降级,如下代码不会产生死锁。

ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock"); rtLock.readLock().lock();
System.out.println("get read lock");

以上这段代码虽然不会导致死锁,但没有正确的释放锁。从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。、
============以下我会通过一个真实场景下的缓存机制来讲解 ReentrantReadWriteLock 实际应用============

1 class CachedData {
2 Object data;
3 volatile boolean cacheValid;
4 final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
5
6 public void processCachedData() {
7 rwl.readLock().lock(); //读锁加上后 可以多线程同时使用读锁;---- 1
8 if (!cacheValid) { //其中一个线程进入判断,判断为空 则进入if语句,然后将读锁解锁
9 // Must release read lock before acquiring write lock
10 rwl.readLock().unlock(); //读锁解锁 此时读锁解锁后 在 1 处处在阻塞的写线程获得执行权 进入该部分代码 然后加锁
11 rwl.writeLock().lock(); //写锁加锁
12 try {
13 // Recheck state because another thread might have,acquired write lock and changed state before we did.
14 if (!cacheValid) { //此处必须进行判断 如果不判断 此下边锁降级后 上边的多个读线程执行到此后对执行赋值代码 出现多次赋值的问题
15 data = ...
16 cacheValid = true;
17 }
18 // 在释放写锁之前通过获取读锁降级写锁(注意此时还没有释放写锁)
19 rwl.readLock().lock(); //对写锁 进行 锁降级
20 } finally {
21 rwl.writeLock().unlock(); // 释放写锁而此时已经持有读锁
22 }
23 }
24
25 try {
26 use(data);
27 } finally {
28 rwl.readLock().unlock();
29 }
30 }
31 }

以上代码加锁的顺序为:
1. rwl.readLock().lock();
2. rwl.readLock().unlock();
3. rwl.writeLock().lock();
4. rwl.readLock().lock(); //锁降级
5. rwl.writeLock().unlock();
6. rwl.readLock().unlock();
以上过程整体讲解:
1. 多个线程同时访问该缓存对象时,都加上当前对象的读锁,之后其中某个线程优先查看data数据是否为空。【加锁顺序序号:1 】
2. 当前查看的线程发现没有值则释放读锁立即加上写锁,准备写入缓存数据。(不明白为什么释放读锁的话可以查看上面讲解进入写锁的前提条件)【加锁顺序序号:2和3 】
3. 为什么还会再次判断是否为空值(!cacheValid)是因为第二个、第三个线程获得读的权利时也是需要判断是否为空,否则会重复写入数据。
4. 写入数据后先进行读锁的降级后再释放写锁。【加锁顺序序号:4和5 】
5. 最后数据数据返回前释放最终的读锁。【加锁顺序序号:6 】
如果不使用锁降级功能,如先释放写锁,然后获得读锁,在这个get过程中,可能会有其他线程竞争到写锁 或者是更新数据 则获得的数据是其他线程更新的数据,可能会造成数据的污染,即产生脏读的问题。
下面,让我们来实现真正趋于实际生产环境中的缓存案例:

1 import java.util.HashMap;
2 import java.util.Map;
3 import java.util.concurrent.locks.ReadWriteLock;
4 import java.util.concurrent.locks.ReentrantReadWriteLock;
5
6 public class CacheDemo {
7 /**
8 * 缓存器,这里假设需要存储1000左右个缓存对象,按照默认的负载因子0.75,则容量=750,大概估计每一个节点链表长度为5个
9 * 那么数组长度大概为:150,又有雨设置map大小一般为2的指数,则最近的数字为:128
10 */
11 private Map<String, Object> map = new HashMap<>(128);
12 private ReadWriteLock rwl = new ReentrantReadWriteLock();
13 public static void main(String[] args) {
14
15 }
16 public Object get(String id){
17 Object value = null;
18 rwl.readLock().lock();//首先开启读锁,从缓存中去取
19 try{
if(map.get(id) == null){ //如果缓存中没有释放读锁,上写锁
22 rwl.readLock().unlock();
23 rwl.writeLock().lock();
24 try{
25 if(value == null){ //防止多写线程重复查询赋值
26 value = "redis-value"; //此时可以去数据库中查找,这里简单的模拟一下
27 }
28 rwl.readLock().lock(); //加读锁降级写锁,不明白的可以查看上面锁降级的原理与保持读取数据原子性的讲解
29 }finally{
30 rwl.writeLock().unlock(); //释放写锁
31 }
32 }
33 }finally{
34 rwl.readLock().unlock(); //最后释放读锁
35 }
36 return value;
37 }
38 }

提示:读写锁之后有一个与它配合使用的有条件的阻塞,可以实现线程间的通信,它就是Condition。具体参考博客:并发库应用之六 & 有条件阻塞Condition应用
concrrent类下ReentrantReadWriteLock类的原理以及使用的更多相关文章
- ReentrantReadWriteLock类和ReentrantLock类的区别
Java.util.concurrent.locks包定义了两个锁类,ReentrantLock和ReentrantReadWriteLock类. 当有很多线程都从某个数据结构中读取数据而很少有线程对 ...
- (四)Lock,ReentrantLock,ReentrantReadWriteLock类的使用以及相关api---synchronized进阶
这篇博客记录了Lock,ReentrantLock,ReentrantReadWriteLock类的使用以及其一些api: 码字不易~~另外<java多线程编程核心技术>这本书读着很爽 前 ...
- java面向对象程序设计(下)-枚举类
在某些情况下,一个类的对象是有限而且固定的,比如季节类,它只有4个对象;再比如行星类,目前只有8个对象,这些实例有限而且固定的类,在Java中被称为枚举类 JDK1.5新增了一个enum关键字,(它与 ...
- 30个类手写Spring核心原理之自定义ORM(上)(6)
本文节选自<Spring 5核心原理> 1 实现思路概述 1.1 从ResultSet说起 说到ResultSet,有Java开发经验的"小伙伴"自然最熟悉不过了,不过 ...
- 30个类手写Spring核心原理之动态数据源切换(8)
本文节选自<Spring 5核心原理> 阅读本文之前,请先阅读以下内容: 30个类手写Spring核心原理之自定义ORM(上)(6) 30个类手写Spring核心原理之自定义ORM(下)( ...
- python类中的super,原理如何?MRO是什么东东?
下面这个URL解释得比较清楚. http://python.jobbole.com/86787/?utm_source=group.jobbole.com&utm_medium=related ...
- VS2010引用App_Code下的类文件问题解决方法
原文连接:http://blog.csdn.net/zjlovety/article/details/7658528 VS2020中“添加ASP.NET文件夹”里没有App_Code,添加普通文件夹然 ...
- C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域
面向对象编程 --继承情况下的类作用域 引言: 在继承情况下,派生类的作用域嵌套在基类作用域中:假设不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义. 正是这样的类作用域的层次嵌套使 ...
- WAS下获取包路径下所有类
最近做javaweb项目的混淆工作,用到proguard,该工具混淆.jar文件比较方便,故把所有项目代码和配置文件打成jar包, 生成的jar包经过proguard处理后,再次打包(解决progua ...
随机推荐
- Ubuntu 18.04基础软件安装
1.fcitx 这个应该是最基础的了,虽然系统自带的也有,不过说实话可能是我还不会配置,我觉得是不好用,坚持用了一周后还是换回了小企鹅,最初是装小企鹅时失败了被迫坚持用了一周,当时失败情况是这样的,使 ...
- spark面试总结1
Spark Core面试篇01 一.简答题 1.Spark master使用zookeeper进行HA的,有哪些元数据保存在Zookeeper? 答:spark通过这个参数spark.deploy.z ...
- Effective Java 第三版——60. 需要精确的结果时避免使用float和double类型
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- 解决使用display:inline-block时元素间隔问题
在写个人博客页时出现了一个百思不得其解的问题,不知道为什么出现以下情况: 可以看到,下面的a标签中出现了一个间隔,怎么都去不掉,查阅资料后发现,问题原因如下: 1.第一种解决办法,去掉HTML元素中的 ...
- GODOT 3.0 开发进度汇报 #7
由于原文采取了记流水账的方式,觉得没有必要照直翻译了,就只选取了其中的主要信息. GDNative C++ 语言绑定 进行了重写以便Godot更好的生成和处理脚本. D 语言绑定 也正在积极开发中. ...
- python 2解决编码问题
import sys reload(sys) sys.setdefaultencoding('utf-8') 另:python 3的open函数可以直接加encoding参数
- deque源码3(deque的构造与内存、ctor、push_back、push_front)
deque源码1(deque概述.deque中的控制器) deque源码2(deque迭代器.deque的数据结构) deque源码3(deque的构造与内存.ctor.push_back.push_ ...
- mysql 开发基础系列1 表查询操作
在安装完数据库后,不管是windows 还是linux平台, mysql的sql命令都大同小异,相关命令都是相同的,每个命令结束后 都以 ; 结尾, 注意在windows平台中表名是不区分大小写 ...
- Java并发编程之AQS
一.什么是AQS AQS(AbstractQueuedSynchronize:队列同步器)是用来构建锁或者其他同步组件的基础框架,很多同步类都是在它的基础上实现的,比如常用的ReentrantLock ...
- 线程池ThreadPool及Task调度死锁分析
近1年,偶尔发生应用系统启动时某些操作超时的问题,特别在使用4核心Surface以后.笔记本和台式机比较少遇到,服务器则基本上没有遇到过. 这些年,我写的应用都有一个习惯,就是启动时异步做很多准备工作 ...