• 【java并发编程】ReentrantLock 可重入读写锁


    一、ReentrantLock可重入锁

    可重入锁ReentrantLock 是一个互斥锁,即同一时间只有一个线程能够获取锁定资源,执行锁定范围内的代码。这一点与synchronized 关键字十分相似。其基本用法代码如下:

    Lock lock = new ReentrantLock();  //实例化锁
    //lock.lock(); //上锁
    boolean locked = lock.tryLock();  //尝试上锁
    if(locked){
      try {
        //被锁定的同步代码块,同时只能被一个线程执行
      }finally {
        lock.unlock(); //放在finally代码块中,保证锁一定会被释放
      }
    }
    

    通过lock函数获取锁,通过unlock函数释放锁。非常重要的是,需要把需要同步执行的代码放入 try/finally 代码块中,并在finally中将锁释放。ReentrantLock是可重入锁,即:(lock/unlok)动作里面可以嵌套(lock/unlock),针对同一个锁可以多次嵌套使用,不会产生死锁。但是lock函数与unlock函数在代码中必须成对出现,否则会出现死锁

    二、ReentrantReadWriteLock读写锁

    ReentrantReadWriteLock类为读写锁实现类,针对某一个对象或可变变量,只要没有线程在修改它,这个对象或可变变量就可以同时被多个线程读取。ReentrantReadWriteLock将锁分为读锁和写锁,只要没有线程持有写锁的情况下,读锁可以由多个线程同时持有。

    • 读锁-如果没有线程获取或请求写锁,那么多个线程可以获取读锁
    • 写锁-如果没有线程在读或写,那么只有一个线程可以获得写锁

    简单的说就是ReentrantReadWriteLock可以保证最多同时有一个线程在写数据,或者可以同时有多个线程读数据。因此使用ReentrantReadWriteLock,在读操作比写操作更频繁的情况下,可以提高程序的性能和吞吐量。

    下面我们用一个简单的例子,来解读一下如何应用读写锁。

    public class TestReadWriteLock {
      //可以同时执行3个线程任务的线程池
      ExecutorService executor = Executors.newFixedThreadPool(3);
      //读写目标,写线程放入数据到map,读线程从map读取数据
      Map<String, String> map = new HashMap<>();
      //读写锁操作对象
      ReadWriteLock lock = new ReentrantReadWriteLock();
    
      //写操作函数
      public void write(){
        executor.submit(() -> { //线程池提交写操作任务
          lock.writeLock().lock(); //加写锁
          try {
            map.put("key", "val");  //写数据操作
            Thread.sleep(2000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          } finally {
            lock.writeLock().unlock(); //释放写锁
          }
        });
      }
    
      //读操作函数
      public void read(){
        lock.readLock().lock(); //加读锁
        System.out.println(Thread.currentThread().getName() + "加读锁");
        try {
          System.out.println(map.get("key")); //读数据操作
        } finally {
          lock.readLock().unlock(); //释放读锁
          System.out.println(Thread.currentThread().getName() + "释放读锁");
        }
      }
    
    }
    

    三、读锁之间不互斥

    我们写一个测试方法,通过打印输出来理解读写锁控制代码的执行顺序。

      //测试
      public static void main(String[] args) {
        TestReadWriteLock test = new TestReadWriteLock();
        test.write();  //提交一次写操作任务,写一条数据
        Runnable readTask = test::read;  //线程方法read,实现线程Runnable接口的简便写法
        test.executor.submit(readTask);  //读1次(新读线程)
        test.executor.submit(readTask);  //读2次 (新读线程)
        test.executor.shutdown();
      }
    

    执行上面的代码,可能会出现下面的输出

    pool-1-thread-2加读锁
    pool-1-thread-3加读锁
    val
    val
    pool-1-thread-3释放读锁
    pool-1-thread-2释放读锁
    

    在pool-1-thread-2没有释放读锁情况下,pool-1-thread-3可以再次加读锁,并且都正确的读取到数据val。说明读锁之间是不互斥的。但是,在进行读操作(读锁生效)的时候,写操作是无法进行的(无法获取写锁),所以ReentrantReadWriteLock不支持同时加读锁和写锁。 这个结论我可以负责任告诉大家,这里我就不做验证了!

    欢迎关注我的博客,更多精品知识合集

    本文转载注明出处(必须带连接,不能只转文字):字母哥博客 - zimug.com

    觉得对您有帮助的话,帮我点赞、分享!您的支持是我不竭的创作动力!。另外,笔者最近一段时间输出了如下的精品内容,期待您的关注。

  • 相关阅读:
    gRPC四种通信模式
    4383 [八省联考 2018] 林克卡特树(WQS 二分+DP)
    线程与进程的区别与联系
    Linux内核源码分析 (B.2)深入理解 Linux 物理内存管理
    企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图
    cpp学习笔记:STL vector
    C语言(结构体)
    全局平均池化/全局最大池化Pytorch实现:
    用Docker部署SSM项目
    【数据结构初阶】树+二叉树+堆的实现+堆的应用
  • 原文地址:https://www.cnblogs.com/zimug/p/16272252.html