首页 > 其他分享 >多线程系列(十) -ReadWriteLock用法详解

多线程系列(十) -ReadWriteLock用法详解

时间:2024-02-27 10:04:15浏览次数:25  
标签:count Thread ReadWriteLock 详解 线程 lock new 多线程 public

一、摘要

在上篇文章中,我们讲到ReentrantLock可以保证了只有一个线程能执行加锁的代码。

但是有些时候,这种保护显的有点过头,比如下面这个方法,它仅仅就是只读取数据,不修改数据,它实际上允许多个线程同时调用的。

public class Counter {

    private final Lock lock = new ReentrantLock();

    private int count;

    public int get() {
        // 加锁
        lock.lock();
        try {
            return count;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

站在程序性能的角度,实际上我们想要的是这样的效果。

  • 1.读和读之间不互斥,因为只读操作不会有数据安全问题
  • 2.写和写之间互斥,避免一个写操作影响另外一个写操作,引发数据计算错误问题
  • 3.读和写之间互斥,避免读操作的时候写操作修改了内容,引发数据脏读问题

总结起来就是,允许多个线程同时读,但只要有一个线程在写,其他线程就必须排队等待。

在 JDK 中有一个读写锁ReadWriteLock,使用它就可以解决这个问题,它可以保证以下两点:

  • 1.只允许一个线程写入,其他线程既不能写入也不能读取
  • 2.没有写入时,多个线程允许同时读,可以提高程序并发性能

实际上,读写锁ReadWriteLock里面有两个锁实现,一个是读操作相关的锁,称为共享锁,当多个线程同时操作时,不会让多个线程进行排队等待,大大的提升了程序并发读的执行效率;另一个是写操作相关的锁,称为排他锁,当多个线程同时操作时,只允许一个线程写入,其他线程进入排队等待;两者进行组合操作,就可以实现上面的预期效果。

下面我们一起来看看它的基本用法!

二、ReadWriteLock 基本用法

2.1、读和读共享

读和读之间不互斥,当多个线程进行读的时候,不会让多个线程进行排队等待。

我们可以看一个简单的例子!

public class Counter {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    private int count;

    public void read() {
        // 加读锁
        lock.readLock().lock();
        try {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了读锁,count:" + count);
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放读锁
            lock.readLock().unlock();
        }
    }
}
public class MyThreadTest {

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.read();
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.read();
            }
        });

        threadA.start();
        threadB.start();
    }
}

看一下运行结果:

2023-10-23 16:12:28:119 当前线程:Thread-0获得了读锁,count:0
2023-10-23 16:12:28:119 当前线程:Thread-1获得了读锁,count:0

从日志时间上可以很清晰的看到,尽管加锁了,并且休眠了 5 秒,但是两个线程还是几乎同时执行try()方法里面的代码,证明了读和读之间是不互斥的,可以显著提高程序的运行效率。

2.2、写和写之间互斥

写和写之间互斥,当多个线程进行写的时候,只允许一个线程写入,其他线程进入排队等待。

我们可以看一个简单的例子!

public class Counter {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    private int count;

    public void write() {
        // 加写锁
        lock.writeLock().lock();
        try {
            count++;
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了写锁,count:" + count);
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放写锁
            lock.writeLock().unlock();
        }
    }
}
public class MyThreadTest {

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.write();
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.write();
            }
        });

        threadA.start();
        threadB.start();
    }
}

看一下运行结果:

2023-10-23 16:29:59:103 当前线程:Thread-0获得了写锁,count:1
2023-10-23 16:30:04:108 当前线程:Thread-1获得了写锁,count:2

从日志时间上可以很清晰的看到,两个线程进行串行执行,证明了写和写之间是互斥的。

2.3、读和写之间互斥

读和写之间互斥,当多个线程交替进行读写的时候,操作上互斥,只有一个线程能进入,其他线程进入排队等待。

我们可以看一个简单的例子!

public class Counter {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    private int count;

    public void read() {
        // 加读锁
        lock.readLock().lock();
        try {
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了读锁,count:" + count);
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放读锁
            lock.readLock().unlock();
        }
    }

    public void write() {
        // 加写锁
        lock.writeLock().lock();
        try {
            count++;
            String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
            System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了写锁,count:" + count);
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放写锁
            lock.writeLock().unlock();
        }
    }
}

public class MyThreadTest {

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.read();
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.write();
            }
        });

        threadA.start();
        threadB.start();
    }
}

看一下运行结果:

2023-10-23 16:36:08:786 当前线程:Thread-0获得了读锁,count:0
2023-10-23 16:36:13:791 当前线程:Thread-1获得了写锁,count:1

从日志时间上可以很清晰的看到,两个线程进行串行执行,证明了读和写之间是互斥的。

三、小结

总结下来,ReadWriteLock有以下特点:

  • 允许多个线程在没有写入时同时读取,可以提高读取效率
  • 当存在写入情况时,只允许一个线程写入,其他线程进入排队等待
  • 适合读多写少的场景

对于同一个数据,有大量线程读取,但仅有少数线程修改,使用ReadWriteLock可以显著的提升程序并发执行效率。

例如,一个论坛的帖子,浏览可以看做读取操作,是非常频繁的,而回复可以看做写入操作,它是不频繁的,这种情况就可以使用ReadWriteLock来实现。

本文主要围绕ReadWriteLock的基本使用做了一次知识总结,如果有不正之处,请多多谅解,并欢迎批评指出。

四、参考

1、https://www.cnblogs.com/xrq730/p/4855631.html

2、https://www.liaoxuefeng.com/wiki/1252599548343744/1306581002092578

标签:count,Thread,ReadWriteLock,详解,线程,lock,new,多线程,public
From: https://www.cnblogs.com/dxflqm/p/18036245

相关文章

  • 机器学习策略篇:详解单一数字评估指标(Single number evaluation metric)
    单一数字评估指标无论是调整超参数,或者是尝试不同的学习算法,或者在搭建机器学习系统时尝试不同手段,会发现,如果有一个单实数评估指标,进展会快得多,它可以快速告诉,新尝试的手段比之前的手段好还是差。所以当团队开始进行机器学习项目时,经常推荐他们为问题设置一个单实数评估指标。......
  • 深入解析Python并发编程的多线程和异步编程
    本文分享自华为云社区《Python并发编程探秘:多线程与异步编程的深入解析》,作者:柠檬味拥抱。在Python编程中,多线程是一种常用的并发编程方式,它可以有效地提高程序的执行效率,特别是在处理I/O密集型任务时。Python提供了threading模块,使得多线程编程变得相对简单。本文将深入探讨thre......
  • 【性能测试】Redis中的缓存雪崩、缓存击穿、缓存穿透问题详解
    一.什么是缓存雪崩当我们提到缓存系统中的问题,缓存雪崩是一个经常被讨论的话题。缓存雪崩是指在某一时刻发生大量的缓存失效,导致瞬间大量的请求直接打到了数据库,可能会导致数据库瞬间压力过大甚至宕机。尤其在高并发的系统中,这种情况会导致连锁反应,整个系统可能会崩溃。1.......
  • 核心子方法2: obtainFreshBeanFactory()方法详解
    先总结: 该方法new了一个beanFactory, 设置了一些忽略的接口, 加载并解析了bean.xml, 主要将bean信息解析为BeanDefinition保存到beanFactory中1.refreshBeanFactory()方法 1.1创建DefaultListableBeanFactory对象: createBeanFactory()->newDefaultListableBeanF......
  • QT多线程实现-----问题解决及实现方式
    一、概述恰巧正在做一个多线程通信的项目,具体功能是与下位机交互和文件发送,在子线程中实现命令发送和文件传输。使用movetothread主要遇到以下几个问题:1.Socketnotifierscannotbeenabledordisabledfromanotherthread。2.子线程完成文件传输,发送信号......
  • 利用IO复用技术Epoll与线程池实现多线程的Reactor高并发模型
    Reactor模型是一种常见的高并发设计模式,特别是在网络编程中。在Reactor模型中,一个或多个输入同时传递给一个或多个服务处理程序。服务处理程序对输入进行处理,然后将结果传递给相应的输出处理程序。使用IO复用技术(如epoll)和线程池,可以实现多线程的Reactor高并发模型。下面是一个简......
  • 详解SSL证书系列(1)什么是SSL证书?
    你一定遇到过这种情况,打开一个网站,浏览器弹出警告”您与此网站之间建立的连接不安全。由于此连接不安全,因此信息(如密码或信用卡)不会安全地发送到此网站,并且可能被其他人截获或看到”。细心的你也一定关注到,有的网址是https开头的,有的是http。https开头的网站前面,会有一把小锁。......
  • JavaScript中的包装类型详解
    JavaScript中的包装类型详解在JavaScript中,我们有基本类型和对象类型两种数据类型。基本类型包括String,Number,Boolean,null,undefined和Symbol。然而,当我们需要在这些基本类型上调用方法时,就需要用到JavaScript的包装类型。什么是包装类型?包装类型是JavaScript中的......
  • BFC详解
    1.问题1.1BFC作用一:可以取消盒子margin塌陷计算BFC的高度时,考虑BFC所包含的所有元素,连浮动元素也参与计算1.2BFC作用二:可以阻止该元素被浮动元素覆盖浮动盒区域不叠加到BFC上!!!这是BFC的独立性主要应用于一行中同时存在浮动元素和行内元素,防止行内元素被覆盖(不常用,一般一行均......
  • 多线程系列(九) -ReentrantLock常用方法详解
    一、简介在上一篇文章中,我们介绍了ReentrantLock类的一些基本用法,今天我们重点来介绍一下ReentrantLock其它的常用方法,以便对ReentrantLock类的使用有更深入的理解。二、常用方法介绍2.1、构造方法ReentrantLock类有两个构造方法,核心源码内容如下:/***默认创建非公平锁*/......