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 整合 Dubbo/ZooKeeper 实现 SOA 案例解析

    Springboot 整合 Dubbo/ZooKeeper 实现 SOA 案例解析

    这篇文章主要介绍了Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例,需要的朋友可以参考下
    2017-11-11
  • java基础详细笔记之异常处理

    java基础详细笔记之异常处理

    异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的,下面这篇文章主要给大家介绍了关于java基础详细笔记之异常处理的相关资料,需要的朋友可以参考下
    2022-03-03
  • Java实现单线程聊天室

    Java实现单线程聊天室

    这篇文章主要为大家详细介绍了Java实现单线程聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • 如何使用IDEA 搭建 SpringCloud 项目

    如何使用IDEA 搭建 SpringCloud 项目

    所谓微服务,就是要把整个业务模块拆分成多个各司其职的小模块,做到单一职责原则,不会重复开发相同的业务代码,实现真正意义上的高内聚、低耦合,这篇文章主要介绍了如何使用IDEA 搭建 SpringCloud 项目,需要的朋友可以参考下
    2023-11-11
  • SpringBoot 整合Redis 数据库的方法

    SpringBoot 整合Redis 数据库的方法

    Redis是一个基于内存的日志型可持久化的缓存数据库,保存形式为key-value格式,Redis完全免费开源,它使用ANSI C语言编写。这篇文章主要介绍了SpringBoot 整合Redis 数据库的方法,需要的朋友可以参考下
    2018-03-03
  • 图文结合讲解Java单例模式

    图文结合讲解Java单例模式

    这篇文章主要以图文结合的方式为大家详细介绍了Java单例模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • 使用Springboot实现OAuth服务的示例详解

    使用Springboot实现OAuth服务的示例详解

    OAuth(Open Authorization)是一个开放标准,用于授权第三方应用程序访问用户资源,而不需要共享用户凭证。本文主要介绍了如何使用Springboot实现一个OAuth服务,需要的可以参考一下
    2023-05-05
  • Java基础异常处理代码及原理解析

    Java基础异常处理代码及原理解析

    这篇文章主要介绍了java基础异常处理代码及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • 深入分析JAVA流程控制语句

    深入分析JAVA流程控制语句

    这篇文章主要介绍了JAVA流程控制语句的的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • Java 反射类型Type的用法说明

    Java 反射类型Type的用法说明

    这篇文章主要介绍了Java 反射类型Type的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-05-05

最新评论