一、概念

Java常见的多是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升,读写锁能有效提高读比写多的场景下的程序性能,比排它锁好。

Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。

读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!如果只是使用Lock,那么读和读的线程之前也会互斥。

二、读写锁特性

ReadWriteLock仅定义了获取读锁和写锁的两个方法,即readLock()和writeLock()方法。

public interface ReadWriteLock {/*** Returns the lock used for reading.** @return the lock used for reading*/Lock readLock();/*** Returns the lock used for writing.** @return the lock used for writing*/Lock writeLock();
}

ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。它的特性可以总结为三个:

(1)公平性选择

非公平模式(默认)
当以非公平初始化时,读锁和写锁的获取的顺序是不确定的。非公平锁主张竞争获取,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量。

公平模式
当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。

(2)重入性

什么是可重入锁,不可重入锁呢?"重入"字面意思已经很明显了,就是可以重新进入。可重入锁,就是说一个线程在获取某个锁后,还可以继续获取该锁,即允许一个线程多次获取同一个锁。比如synchronized内置锁就是可重入的,如果A类有2个synchornized方法method1和method2,那么method1调用method2是允许的。显然重入锁给编程带来了极大的方便。假如内置锁不是可重入的,那么导致的问题是:1个类的synchornized方法不能调用本类其他synchornized方法,也不能调用父类中的synchornized方法。与内置锁对应,JDK提供的显示锁ReentrantLock也是可以重入的

(3)锁降级

读写锁支持锁降级,遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级.

不支持所升级代码:

/***Test Code 1**/
package test;import java.util.concurrent.locks.ReentrantReadWriteLock;public class Test1 {public static void main(String[] args) {ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();rtLock.readLock().lock();System.out.println("get readLock.");rtLock.writeLock().lock();System.out.println("blocking");}
}

Result:

结论:上面的测试代码会产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的

支持锁降级demo:

/***Test Code 2**/
package test;import java.util.concurrent.locks.ReentrantReadWriteLock;public class Test2 {public static void main(String[] args) {ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();  rtLock.writeLock().lock();  System.out.println("writeLock");  rtLock.readLock().lock();  System.out.println("get read lock");  }
}

Result:

结论:ReentrantReadWriteLock支持锁降级,上面代码不会产生死锁。这段代码虽然不会导致死锁,但没有正确的释放锁。从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。

三、写锁的获取与释放

写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。

如果存在读锁,则写锁不能被获取,原因在于:读写锁要确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当前写线程的操作。因此只有等待其他读线程都释放了读锁,写锁才能被当前线程所获取,而写锁一旦被获取,则其他读写线程的后续访问均被阻塞。
        写锁的释放比较简单。 每次释放均减少写状态,当写状态为0时表示写锁已被释放,从而等待的读写线程能够继续访问读写锁,同时前次写线程的修改对后续读写线程可见。

四、读锁的获取与释放

读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取也就是一种共享式锁。在没有其他写线程访问(或者写状态为0)时,读锁总会成功的被获取,而所做的也只是(线程安全的)增加读状态。如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已被其他线程获取,则进入等待状态。

五、读写锁应用

代码示例:创建3个读线程,3个写线程:

package cn.itcast.heima;import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockTest {public static void main(String[] args) {final Queue3 q3 = new Queue3();//同时创建3个读取线程,3个写入线程for(int i=0;i<3;i++){new Thread(){public void run(){while(true){q3.get();//读取数据                        }}}.start();new Thread(){public void run(){while(true){q3.put(new Random().nextInt(10000));}}           }.start();}}
}class Queue3{private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。ReadWriteLock rwl = new ReentrantReadWriteLock();//创建读写锁对象//读的方法public void get(){rwl.readLock().lock();//读的时候上读锁try {System.out.println(Thread.currentThread().getName() + " be ready to read data!");Thread.sleep((long)(Math.random()*1000));System.out.println(Thread.currentThread().getName() + "have read data :" + data);            } catch (InterruptedException e) {e.printStackTrace();}finally{rwl.readLock().unlock();}}//写的方法public void put(Object data){rwl.writeLock().lock();//写的时候上写锁try {System.out.println(Thread.currentThread().getName() + " be ready to write data!");                  Thread.sleep((long)(Math.random()*1000));this.data = data;     System.out.println(Thread.currentThread().getName() + " have write data: " + data);                 } catch (InterruptedException e) {e.printStackTrace();}finally{rwl.writeLock().unlock();}}
}

API文档中对ReentrantReadWriteLock示例代码:

/** 示例用法。下面的代码展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理):* 当有许多读的线程访问一个数据实体时,如果有一个线程发现该数据实体为空,该线程就需要将读锁释放,上写锁,写入数据* 然后在释放写锁前重新上读锁*/
class CachedData {Object data;volatile boolean cacheValid;//判断实体中是否有值ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();void processCachedData() {rwl.readLock().lock();if (!cacheValid) {// Must release read lock before acquiring write lockrwl.readLock().unlock();rwl.writeLock().lock();// Recheck state because another thread might have acquired//   write lock and changed state before we did.if (!cacheValid) {data = ...//加载数据cacheValid = true;}// Downgrade by acquiring read lock before releasing write lockrwl.readLock().lock();rwl.writeLock().unlock(); // Unlock write, still hold read}use(data);rwl.readLock().unlock();}
}

面试题:请设计一个缓存系统

缓存系统就是可以装很多个对象。要拿对象别直接找数据库,而是访问缓存系统。当缓存系统的数据被访问时,就应该检查内部是否有该数据,如果有,就直接返回,如果没有就查询数据库,然后把数据存入内存中,下次就直接返回该数据不需要再查数据库了。

package cn.itcast.heima;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {//定义一个Map,存储多个对象private Map<String, Object> cache = new HashMap<String, Object>();public static void main(String[] args) {}private ReadWriteLock rwl = new ReentrantReadWriteLock();//获取对象的方法,当多个线程来读(即获取对象时)不需要互斥,只有写的时候才需要互斥,因此需要使用读写锁public  Object getData(String key){rwl.readLock().lock();//先上读锁,这样多个线程都可以来读Object value = null;try{value = cache.get(key);//如果线程读的时候发现数据为空,那么就把读锁释放,禁止数据的读取,然后上写锁if(value == null){rwl.readLock().unlock();rwl.writeLock().lock();try{//再次判断数据是否存在,因为当一个线程获取写锁完成了数据填充并释放了写锁,另外一个线程也可能获取了写锁,会重复填充数据。if(value==null){value = "aaaa";//实际是去queryDB();即查询数据库}}finally{rwl.writeLock().unlock();//数据填充完,释放写锁}rwl.readLock().lock();//恢复为读锁}}finally{rwl.readLock().unlock();}return value;}
}

总结:

1.Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性

2.ReetrantReadWriteLock读写锁的效率明显高于synchronized关键字

3.ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的

4.ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁

java并发-ReentrantReadWriteLock读写锁相关推荐

  1. ReentrantReadWriteLock读写锁及其在 RxCache 中的使用

    一. ReentrantReadWriteLock读写锁 Lock 是相当于 synchronized 更面向对象的同步方式,ReentrantLock 是 Lock 的实现. 本文要介绍的 Reen ...

  2. java 单例 读写锁_你用对锁了吗?浅谈 Java “锁” 事

    每个时代,都不会亏待会学习的人 大家好,我是yes. 本来打算继续写消息队列的东西的,但是最近在带新同事,发现新同事对于锁这方面有一些误解,所以今天就来谈谈"锁"事和 Java 中 ...

  3. 浅析ReentrantReadWriteLock读写锁

    在并发场景中用于解决线程安全的问题,我们会高频率的使用到独占式锁,通常使用java提供的关键字synchronized或者concurrents包中实现了Lock接口ReentrantLock.它们都 ...

  4. Java并发-ReentrantReadWriteLock源码分析

    ReentrantLock实现了标准的互斥重入锁,任一时刻只有一个线程能获得锁.考虑这样一个场景:大部分时间都是读操作,写操作很少发生:我们知道,读操作是不会修改共享数据的,如果实现互斥锁,那么即使都 ...

  5. android读写锁,ReentrantReadWriteLock读写锁及其在 RxCache 中的使用

    一. ReentrantReadWriteLock读写锁 Lock 是相当于 synchronized 更面向对象的同步方式,ReentrantLock 是 Lock 的实现. 本文要介绍的 Reen ...

  6. ReentrantReadWriteLock(读写锁)

    为了提高性能,java提供了读写锁, 读锁: 在读的地方使用读锁,可以多个线程同时读. 写锁: 在写的地方使用写锁,只要有一个线程在写,其他线程就必须等待 例子: public static Read ...

  7. ReentrantReadWriteLock读写锁(读多写少场景)

    ReentrantReadWriteLock读写锁 适合读多写少的场景. 读锁ReentrantReadWriteLock.ReadLock可以被多个线程同时持有, 所以并发能力很高. 写锁Reent ...

  8. 并发锁之二:ReentrantReadWriteLock读写锁

    一.简介 读写锁是一种特殊的自旋锁,它把对共享资源对访问者划分成了读者和写者,读者只对共享资源进行访问,写者则是对共享资源进行写操作.读写锁在ReentrantLock上进行了拓展使得该锁更适合读操作 ...

  9. java 实现队列读写锁_史上最全的Java并发系列之Java中的锁的使用和实现介绍(二)...

    前言 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/bin392328206/six-finger 种一棵树最好的时间是十年前,其次是现在 絮叨 上节是锁的第一 ...

最新文章

  1. iphone NSArray 数组越界
  2. 「C++: Eigen」学习笔记
  3. 2通过程序获得环境变量,getenv(),setenv()函数和unsetenv()函数,env查看环境变量,echo输出指定的环境变量
  4. 网站搭建从零开始(二)服务器空间
  5. 基于JAVA+SpringBoot+Mybatis+MYSQL的药店进销存管理系统
  6. Mybatis逆向工程(生成实体类)开发指南
  7. python可以用c_我们可以在Python中使用C代码吗?
  8. centos7配置ip
  9. 细丝菲涅尔衍射MATLAB,任意孔型菲涅尔衍射matlab仿真.docx
  10. hart协议服务器,基于HART协议智能仪表的在线管理系统的设计与实现
  11. vue模板引擎_Vue.js模板引擎理解
  12. 肽核酸PNA-多肽suc-Ala-Ala-Pro-Aaa-pNa|Suc-Ala3-pNA|Pyr-Phe-Leu-pNA
  13. Android获取WIFI 的ssid 方法适配Android9.0
  14. 【Matlab车牌识别】BP神经网络车牌识别【含GUI源码 669期】
  15. C笔记《C Primer Plus 6E》
  16. [python][project][爬虫] 时光网抓取信息
  17. oracle实现aes解密_AES加解密程序的实现
  18. IEC61850缩略语一览表
  19. html文字自动消失了,为什么从网页上复制的文字到word上一修改后面的字就自动消失了...
  20. 关于长尾应用的一些思考

热门文章

  1. Axure高级功能(变量、动态面板[轮播图]、中继器)
  2. QSlider功能作用和信号
  3. js数组操作的一些方法在面试题的使用
  4. 已通过认证的微信公众号名字可以改吗?
  5. 分享10个高质量的插画网站
  6. 物联网专业并不好_在广阔的物联网中,“智能”仍然并不意味着安全
  7. c语言程序设计植树,C语言程序设计实验报告——实验
  8. 关于固态硬盘冷数据掉速问题解决方案
  9. POJ - 3713 (Transferring Sylla)
  10. zepto-selector.js简单分析