读写锁

读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

读操作可以多个线程,写操作只能一个线程

Java并发包提供读写锁的实现是 ReentrantReadWriteLock

特性:

  1. 支持公平性和非公平的锁获取方式
  2. 支持重进入:以读写线程为例,当读线程获取读锁以后,还能再次获取读锁,而写线程在获取写锁时还未完全释放的时候还能再获取写锁以及也能获取读锁。
  3. 锁降级。写锁可以降级为读锁,但是读锁不能升级为写锁

锁降级的定义:

锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

写锁可以降级为读锁顺序:获取写锁----获取读锁------释放写锁------释放读锁。

其缺点:会造成锁饥饿问题 一直读,没有写操作。

资源与锁的三个状态:

  1. 无锁,多线程抢夺资源 乱
  2. 添加锁(Synchronized和ReentrantLock) 都是独占,读读、读写、写写都是独占,每次只能一个操作
  3. 读写锁,读读可以共享,提升性能,同时可以多人进行读操作

ReentrantReadWriteLock 目的就是:提高读操作的吞吐量 (可用于读多写少的情况下)

读写锁可重入的理解:

读锁的重入是允许多个申请读操作的线程,而写锁同时只能允许单个线程占有,该线程的写操作可以重入。

如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,也就是锁的降级。

如果一个线程同时占有了读锁和写锁,在完全释放了写锁,那么就转换为了读锁,以后写操作无法重入,如果写锁未完全释放时,写操作时可以重入的。

失败例子:

  1. package com.RWLock;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.concurrent.TimeUnit;
  5. class MyCache{
  6. private volatile Map<String,Object> map = new HashMap<String,Object>();
  7. //写操作
  8. public void put(String key,Object value) throws InterruptedException {
  9. System.out.println(Thread.currentThread().getName()+"\t------写入数据"+key);
  10. TimeUnit.SECONDS.sleep(1);
  11. map.put(key,value);
  12. System.out.println(Thread.currentThread().getName()+"\t------写入完成"+key);
  13. }
  14. //读操作
  15. public void get(String key) throws InterruptedException {
  16. System.out.println(Thread.currentThread().getName()+"\t------读数据"+key);
  17. TimeUnit.SECONDS.sleep(1);
  18. map.get(key);
  19. System.out.println(Thread.currentThread().getName()+"\t------读取完成"+key);
  20. }
  21. }
  22. public class readWriteLockDemo {
  23. public static void main(String[] args) {
  24. MyCache myCache = new MyCache();
  25. //多个线程进行写操作
  26. for (int i = 1; i <= 5; i++) {
  27. int finalI = i;
  28. new Thread(()->{
  29. try {
  30. myCache.put(finalI +"", finalI +"");
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. },String.valueOf(i)).start();
  35. }
  36. for (int i = 1; i <= 5; i++) {
  37. int finalI = i;
  38. new Thread(()->{
  39. try {
  40. myCache.get(finalI+"");
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. },String.valueOf(i)).start();
  45. }
  46. }
  47. }

使用读写锁以后:

Cache组合一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的读锁和写锁来保证Cache是线程安全的。在读操作get(String key)方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。写操作put(String key,Object value)方法和clear()方法,在更新HashMap时必须提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续。

  1. package com.RWLock;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.concurrent.TimeUnit;
  5. import java.util.concurrent.locks.ReadWriteLock;
  6. import java.util.concurrent.locks.ReentrantReadWriteLock;
  7. class MyCache{
  8. private volatile Map<String,Object> map = new HashMap<String,Object>();
  9. //可重入的读写锁
  10. private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  11. //写操作
  12. public void put(String key,Object value) throws InterruptedException {
  13. try{
  14. readWriteLock.writeLock().lock(); //写锁
  15. System.out.println(Thread.currentThread().getName()+"\t------写入数据"+key);
  16. TimeUnit.SECONDS.sleep(1);
  17. map.put(key,value);
  18. System.out.println(Thread.currentThread().getName()+"\t------写入完成"+key);
  19. }catch (InterruptedException e){
  20. e.printStackTrace();
  21. }finally{
  22. readWriteLock.writeLock().unlock();
  23. }
  24. }
  25. //读操作
  26. public void get(String key) {
  27. try {
  28. readWriteLock.readLock().lock(); //读锁
  29. System.out.println(Thread.currentThread().getName()+"\t------读数据"+key);
  30. TimeUnit.SECONDS.sleep(1);
  31. map.get(key);
  32. System.out.println(Thread.currentThread().getName()+"\t------读取完成"+key);
  33. }catch (InterruptedException e){
  34. e.printStackTrace();
  35. }finally{
  36. readWriteLock.readLock().unlock();
  37. }
  38. }
  39. }
  40. public class readWriteLockDemo {
  41. public static void main(String[] args) {
  42. MyCache myCache = new MyCache();
  43. //多个线程进行写操作
  44. for (int i = 1; i <= 5; i++) {
  45. int finalI = i;
  46. new Thread(()->{
  47. try {
  48. myCache.put(finalI +"", finalI +"");
  49. } catch (Exception e) {
  50. e.printStackTrace();
  51. }
  52. },String.valueOf(i)).start();
  53. }
  54. for (int i = 1; i <= 5; i++) {
  55. int finalI = i;
  56. new Thread(()->{
  57. try {
  58. myCache.get(finalI+"");
  59. } catch (Exception e) {
  60. e.printStackTrace();
  61. }
  62. },String.valueOf(i)).start();
  63. }
  64. }
  65. }

读写锁的设计:依赖于同步器的同步状态实现的。

同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键

如果一个整型变量维护,按位切割,高16位为读状态,低16位为写状态。

读写锁是如何迅速确定读和写各自的状态呢?答案是通过位运算

写锁的获取和释放:

写锁是一个支持重进入的排他锁;

  1. 如果当前线程获取了写锁,则增加写状态,独占
  2. 如果当前线程(A)再获取锁时,读锁已经被获取或者该线程不是已经获取写锁的线程(个人理解:如果有线程获取了写锁,则其他读写线程的后续访问均被阻塞),则当前线程(A)进入等待状态。

获取读锁后不能获取写锁,但是获取写锁后可以获取读锁

读锁的获取和释放

读锁是一个支持重进入的共享锁,它能够被多个线程同时获取,在没有其他写线程访问(或者写状态为0)时,读锁总会被成功地获取,而所做的也只是(线程安全的)增加读状态。

如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁

JUC之读写锁问题的更多相关文章

  1. JUC——线程同步锁(ReentrantReadWriteLock读写锁)

    读写锁简介 所谓的读写锁值得是两把锁,在进行数据写入的时候有一个把“写锁”,而在进行数据读取的时候有一把“读锁”. 写锁会实现线程安全同步处理操作,而读锁可以被多个对象读取获取. 读写锁:ReadWr ...

  2. JUC 并发编程--04 常用的辅助类CountDownLatch , CyclicBarrier , Semaphore , 读写锁 , 阻塞队列,CompletableFuture(异步回调)

    CountDownLatch 相当于一个减法计数器, 构造方法指定一个数字,比如6, 一个线程执行一次,这个数字减1, 当变为0 的时候, await()方法,才开始往下执行,, 看这个例子 Cycl ...

  3. ReadWriteLock读写锁(八)

    前言:在JUC ReentrantReadWriteLock是基于AQS实现的读写锁实现. ReadWriteLock中定义了读写锁需要实现的接口,具体定义如下: public interface R ...

  4. AQS系列(四)- ReentrantReadWriteLock读写锁的释放锁

    前言 继续JUC包中ReentrantReadWriteLock的学习,今天学习释放锁. 一.写锁释放锁 入口方法 public void unlock() { sync.release(1); } ...

  5. JUC-10-ReadWriteLock读写锁

    ReadWriteLock同Lock一样也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个是只读的锁,一个是写锁  

  6. 读写锁ReentrantReadWriteLock源代码浅析

    1.简介 并发中常用的ReentrantLock,是一种典型的排他锁,这类锁在同一时刻只允许一个线程进行访问,实际上将并行操作变成了串行操作.在并发量大的业务中,其整体效率.吞吐量不能满足实现的需要. ...

  7. 读-写锁 ReadWriteLock & 线程八锁

    读-写锁 ReadWriteLock: ①ReadWriteLock 维护了一对相关的锁,一个用于只读操作, 另一个用于写入操作. 只要没有 writer,读取锁可以由 多个 reader 线程同时保 ...

  8. 技术笔记:Delphi多线程应用读写锁

    在多线程应用中锁是一个很简单又很复杂的技术,之所以要用到锁是因为在多进程/线程环境下,一段代码可能会被同时访问到,如果这段代码涉及到了共享资源(数据)就需要保证数据的正确性.也就是所谓的线程安全.之前 ...

  9. java多线程-读写锁

    Java5 在 java.util.concurrent 包中已经包含了读写锁.尽管如此,我们还是应该了解其实现背后的原理. 读/写锁的 Java 实现(Read / Write Lock Java ...

随机推荐

  1. pycharm的破解和基本使用

    pycharm的破解 pycharm的账号注册 在完成安装后打开pycharm软件,需要选择购买或是使用.点击试用,选择进入官网注册账号. 进入官网后选择邮箱登录,输入自己的邮箱,点击sign up ...

  2. [BUUCTF]PWN14——not_the_same_3dsctf_2016

    [BUUCTF]PWN14--not_the_same_3dsctf_2016 题目网址:https://buuoj.cn/challenges#not_the_same_3dsctf_2016 步骤 ...

  3. Vue2与Vue3的组件通讯对比

    Vue2 父传子 父传子比较简单, 主要通过以下步骤实现 父在template中为子绑定属性 <Child :childData='pMsg'/> <!-- 也可以写死 --> ...

  4. Hooks中的useState

    Hooks中的useState React的数据是自顶向下单向流动的,即从父组件到子组件中,组件的数据存储在props和state中,实际上在任何应用中,数据都是必不可少的,我们需要直接的改变页面上一 ...

  5. CF20B Equation 题解

    Content 解方程 \(ax^2+bx+c=0\). 数据范围:\(-10^5\leqslant a,b,c\leqslant 10^5\). Solution 很明显上求根公式. 先来给大家推推 ...

  6. Django的安全机制 CSRF 跨站请求访问

    跨站请求伪造 一.简介 django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成.而对于django中设置防 ...

  7. 实体转为json的,如何处理外键情况

    实体转为json的,如何处理外键情况 jc.registerJsonValueProcessor(Userrelation.class, new JsonValueProcessor() {// 此处 ...

  8. Log4j2 消停了,Logback 开始塌房了?

    今天一早,还没起床,拿起手机赫然看到一个头条信息,标题着实让我心理咯噔了一下! 马上起床,直奔官网,看看到底什么问题?塌的有多厉害? 既然是1.2.9版本以下问题,那就直接找到1.2.9版本修复了些啥 ...

  9. JAVA使用netty建立websocket连接

    依赖 <!-- https://mvnrepository.com/artifact/commons-io/commons-io --> <dependency> <gr ...

  10. JAVA获取多个经纬度的中心点

    import java.util.LinkedList; public class Test1 { /** * 位置实体类,根据自己的来即可 */ static class Position{ /** ...