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 ...
随机推荐
- python(leetcode)-283移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 示例: 输入: [0,1,0,3,12] 输出: [1,3,12,0,0] 说明: 必须在原数组上操作, ...
- 深入Vue2.x的虚拟DOM diff原理
一.前言 Vue的核心是双向绑定和虚拟DOM(下文我们简称为vdom),关于双向绑定可以参阅木琴的文章<剖析Vue原理&实现双向绑定MVVM>,vdom是树状结构,其节点为vnod ...
- ElasticSearch入门1: mac 安装
入门学习顺序: 1. 安装: 1.1 单实例安装: Elastic官方网站: http://www.elastic.co 下载 ElasticSearch: 第一步:点击下载 第二步:点击downlo ...
- redis 系列4 数据结构之链表
一. 概述 链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可能通过增删节点来灵活地调整链表的长度.作为一种数据结构,在C语言中并没有内置的这种数据结构.所以Redis构建了自己的链表实现 ...
- mysql 开发进阶篇系列 4 SQL 优化(各种优化方法点)
1 通过handler_read 查看索引使用情况 如果索引经常被用到 那么handler_read_key的值将很高,这个值代表了一个行被索引值读的次数, 很低的值表明增加索引得到的性能改善不高,索 ...
- 全网最详细的一个超级好用的命令行工具【Cmder】的安装之后的一些配置(图文详解)
不多说,直接上干货! 1.修改命令提示符λ为$ 首先,我们看到 进入解压后的cmder的目录,进入vendor,打开init.bat文件. 修改第15行的代码 @prompt $E[;;40m$P$S ...
- css的direction属性
在项目中有遇到一个问题,select元素中的文字需要右对齐.text-align是无效的,想了很久都没有想到办法.经过一翻搜索之后发现了https://stackoverflow.com/questi ...
- 剑指offer-学习笔记
前言:18/06/06开始学习,每个程序都会用C写一遍,因书中用C++举例,也会换种思路写,供学习和参考!!!很推荐这本书很不错,准备入手,一般不买实体书,都用电子书,因一般都看一遍,但这本会看很多遍 ...
- Python快速学习04:循环 & 函数
前言 系列文章:[传送门] 也就今天认识了 LC ,很开心. 本文目录 循环 for while 中断 函数 函数定义 函数调用 for循环 Python 中的for 循环象shell 脚本里的for ...
- EntityFramework中对象的状态管理(笔记)
刚开始接触EF框架的时候总是不明白: 为什么查询出来的对象 Remove().再 SaveChanges()就会把数据删除.而自己 new 一个Person()对象,然后 Remove()不行? 为什 ...