Java中的ReentrantReadWriteLock实现原理详解

 更新时间:2024年01月13日 09:51:26   作者:java架构师-太阳  
这篇文章主要介绍了Java中的ReentrantReadWriteLock实现原理详解,读写锁实现了接口ReadWriteLock,适合于读多写少的情况,支持公平锁和非公平锁,支持可冲入(进入读锁后可再进入读锁,进入写锁后可再进入写锁和读锁),需要的朋友可以参考下

介绍

读写锁

  • 实现了接口ReadWriteLock
  • 适合于读多写少的情况
  • 支持公平锁和非公平锁
  • 支持可冲入(进入读锁后可再进入读锁,进入写锁后可再进入写锁和读锁)
  • 支持可冲入和公平与非公平

缺点

(1) 写锁饥饿问题,读的线程很多,写的线程抢占不到锁,就一直抢占不到锁,就饥饿

(2) 锁降级,获取写锁后又再次获取读锁(重入),释放了写锁之后就变成了读锁,就是锁降级

内部接口

Sync/ReadLock/WriteLock/FairSync/NonfairSync 是其内部类 下面图有问题,ReadWriteLock没有实现Lock

在这里插入图片描述

代码演示

public class Lock {   
   public static void main(String[] args) {
      new Thread(new Runnable() {
         @Override
         public void run() {
            for (int i = 0; i < 10; i++) {
               Lock.put(i + "", i + "");
            }
         }
      }).start();
      new Thread(new Runnable() {
         @Override
         public void run() {
            for (int i = 0; i < 10; i++) {
               Lock.get(i + "");
            }
         }
      }).start();
   }
   static Map<String, Object> map = new HashMap<String, Object>();
   static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
   static java.util.concurrent.locks.Lock readLock = readWriteLock.readLock();
   static java.util.concurrent.locks.Lock writeLock = readWriteLock.writeLock();
   public static final Object get(String key) {
      readLock.lock();
      try {
         System.out.println("正在做读的操作,key:" + key + "开始");
         Thread.sleep(100);
         Object object = map.get(key);
         System.out.println("正在做读的操作,key:" + key + "结束");
         System.out.println();
         return object;
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         readLock.unlock();
      }
      return key;
   }
   public static final Object put(String key, Object value) {
      writeLock.lock();
      try {
         System.out.println("正在做写的操作,key:" + key + ",value:" + value + "开始");
         Thread.sleep(100);
         Object object = map.put(key, value);
         System.out.println("正在做写的操作,key:" + key + ",value:" + value + "结束");
         System.out.println();
         return object;
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         writeLock.unlock();
      }
      return value;
   }
   public static final void clear() {
      writeLock.lock();
      try {
         map.clear();
      } finally {
         writeLock.unlock();
      }
   }
}
class MyResource {

    Map<String,String> map = new HashMap<>();
    Lock lock = new ReentrantLock();
    ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void write(String key ,String value) {
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t"+"正在写入");
            map.put(key,value);
            //暂停毫秒
            try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"完成写入");
        }finally {
            rwLock.writeLock().unlock();
        }
    }

    public void read(String key) {
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t"+"正在读取");
            String result = map.get(key);

            // 暂停2000毫秒,演示读锁没有完成之前,写锁无法获得
            try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"完成读取"+"\t"+result);
        }finally {
            rwLock.readLock().unlock();
        }
    }


}


/**
 * @auther zzyy
 * @create 2022-04-08 18:18
 */
public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyResource myResource = new MyResource();

        for (int i = 1; i <=10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.write(finalI +"", finalI +"");
            }, String.valueOf(i)).start();
        }

        for (int i = 1; i <=10; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.read(finalI +"");
            },String.valueOf(i)).start();
        }

        // 暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        // 在读锁没有完成时, 写锁不能写入
        for (int i = 1; i <=3; i++) {
            int finalI = i;
            new Thread(() -> {
                myResource.write(finalI +"", finalI +"");
            },"新写锁线程->"+String.valueOf(i)).start();
        }
    }
}

实现原理

从表面来看,ReadLock和WriteLock是两把锁,实际上它只是同一把锁的两个视图而已。

什么叫两个视图呢?可以理解为是一把锁,线程分成两类:读线程和写线程。读线程和写线程之间不互斥(可以同时拿到这把锁),读线程之间不互斥,写线程之间互斥

从下面的构造方法也可以看出,readerLock和writerLock实际共用同一个sync对象。

sync对象同互斥锁一样,分为非公平和公平两种策略,并继承自AQS

public ReentrantReadWriteLock() {
    this(false);           
}
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();    // fair为false使用非公平锁,true使用公平锁
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

同互斥锁一样,读写锁也是用state变量来表示锁状态的。只是state变量在这里的含义和互斥锁完全不同。在内部类Sync中,对state变量进行了重新定义,如下所示:

abstract static class Sync extends AbstractQueuedSynchronizer {
  // ...
  static final int SHARED_SHIFT = 16;
  static final int SHARED_UNIT = (1 << SHARED_SHIFT);
  static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
  static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
  // 持有读锁的线程的重入次数
  static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
  // 持有写锁的线程的重入次数
  static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
  // ...
}

把 state变量拆成两半,低16位用来记录写锁。但同一时间既然只能有一个线程写,为什么还需要16位呢?这是因为一个写线程可能多次重入。

例如,低16位的值等于5,表示一个写线程重入了5次 高16位,用来读锁。

如高16位的值等于5,既可以表示5个读线程都拿到了该锁;也可以表示一个读线程重入了5次

为什么要把一个int类型变量拆成两半,而不是用两个int型变量分别表示读锁和写锁的状态呢?因为无法用一次CAS同时操作两个int变量,所以用了一个int型的高16位和低16位分别表示读锁和写锁的状态

当state=0时,说明既没有线程持有读锁,也没有线程持有写锁;当state != 0时,要么有线程持有读锁,要么有线程持有写锁,两者不能同时成立,因为读和写互斥。

这时再进一步通过sharedCount(state)和exclusiveCount(state)判断到底是读线程还是写线程持有了该锁

到此这篇关于Java中的ReentrantReadWriteLock实现原理详解的文章就介绍到这了,更多相关ReentrantReadWriteLock原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Springboot项目快速实现过滤器功能

    Springboot项目快速实现过滤器功能

    上篇文章已经给大家介绍了Springboot项目如何快速实现Aop功能,这篇文章给大家介绍Springboot项目如何快速实现过滤器功能,感兴趣的小伙伴可以参考阅读
    2023-03-03
  • SpringBoot+@EnableScheduling使用定时器的常见案例

    SpringBoot+@EnableScheduling使用定时器的常见案例

    项目开发中经常需要执行一些定时任务,本文主要介绍了SpringBoot+@EnableScheduling使用定时器的常见案例,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • 解决SpringCloud Config结合github无法读取配置的问题

    解决SpringCloud Config结合github无法读取配置的问题

    这篇文章主要介绍了解决SpringCloud Config结合github无法读取配置的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • SpringBoot接收前端参数的几种方式分享

    SpringBoot接收前端参数的几种方式分享

    这篇文章给大家分享几种SpringBoot接收前端参数的方式,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2023-09-09
  • Java协程编程之Loom项目实战记录

    Java协程编程之Loom项目实战记录

    这篇文章主要介绍了Java协程编程之Loom项目尝鲜,如果用尝鲜的角度去使用Loom项目,可以提前窥探JVM开发者们是如何基于协程这个重大特性进行开发的,这对于提高学习JDK内核代码的兴趣有不少帮助,需要的朋友可以参考下
    2021-08-08
  • 利用Java8 Optional如何避免空指针异常详解

    利用Java8 Optional如何避免空指针异常详解

    Optional可以让你的代码具有可读性,且会避免出现空指针异常。 下面这篇文章主要给大家介绍了关于利用Java8 Optional如何避免空指针异常的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下。
    2018-01-01
  • Java 画时钟遇到的问题及解决方案

    Java 画时钟遇到的问题及解决方案

    我是一个刚入门的小菜鸟,希望我写的东西可以帮助和我一样刚入门的兄弟们少走一些弯路,也希望大佬们可以多指点指点我。感谢!解决在画时钟遇到的问题让我花费不少时间...说两个困扰我比较久的
    2021-11-11
  • Springboot 如何关闭自动配置

    Springboot 如何关闭自动配置

    这篇文章主要介绍了Springboot 如何关闭自动配置的操作,具有很好的开车价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 解决mybatis一对多关联查询多条数据只显示一条的问题

    解决mybatis一对多关联查询多条数据只显示一条的问题

    这篇文章主要介绍了解决mybatis一对多关联查询多条数据只显示一条的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • springboot+nginx+https+linux实现负载均衡加域名访问简单测试

    springboot+nginx+https+linux实现负载均衡加域名访问简单测试

    这篇文章主要介绍了springboot+nginx+https+linux实现负载均衡加域名访问简单测试,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-05-05

最新评论