通俗易懂读写锁ReentrantReadWriteLock的使用

这篇具有很好参考价值的文章主要介绍了通俗易懂读写锁ReentrantReadWriteLock的使用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

ReentrantReadWriteLock不知道大家熟悉吗?其实在实际的项目中用的比较少,反正我所在的项目没有用到过。

ReentrantReadWriteLock称为读写锁,它提供一个读锁,支持多个线程共享同一把锁。它也提供了一把写锁,是独占锁,和其他读锁或者写锁互斥,表明只有一个线程能持有锁资源。通过两把锁的协同工作,能够最大化的提高读写的性能,特别是读多写少的场景,而往往大部分的场景都是读多写少的。

本文主要讲解ReentrantReadWriteLock的使用和应用场景。

ReentrantReadWriteLock介绍

ReentrantReadWriteLock实现了ReadWriteLock接口,可以获取到读锁(共享锁),写锁(独占锁)。同时,通过构造方法可以创建锁本身是公平锁还是非公锁。

读写锁机制:

读锁 写锁
读锁 共享 互斥
写锁 互斥 互斥

线程进入读锁的前提条件:

  • 没有其他线程的写锁
  • 没有写请求,或者有写请求但调用线程和持有锁的线程是同一个线程

进入写锁的前提条件:

  • 没有其他线程的读锁
  • 没有其他线程的写锁

锁升级、降级机制:

我们知道ReentrantLock具备可重入的能力,即同一个线程多次获取锁,不引起阻塞,那么ReentrantReadWriteLock关于可重入性是怎么样的呢?

关于这个问题需要引入两个概念,锁升级,锁降级。

  • 锁升级:从读锁变成写锁。
  • 锁降级:从写锁变成读锁;

重入时锁升级不支持:持有读锁的情况下去获取写锁会导致获取写锁永久等待,需要先释放读,再去获得写

重入时锁降级支持:持有写锁的情况下去获取读锁,造成只有当前线程会持有读锁,因为写锁会互斥其他的锁

API介绍

构造方法:

  • public ReentrantReadWriteLock():默认构造方法,非公平锁
  • public ReentrantReadWriteLock(boolean fair):true 为公平锁

常用API:

  • public ReentrantReadWriteLock.ReadLock readLock():返回读锁
  • public ReentrantReadWriteLock.WriteLock writeLock():返回写锁
  • public void lock():加锁
  • public void unlock():解锁
  • public boolean tryLock():尝试获取锁

代码范式

  • 加解锁格式
r.lock();
try {
    // 临界区
} finally {
	r.unlock();
}
复制代码
  • 锁降级
w.lock();
try {
    r.lock();// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存
    try {
        // ...
    } finally{
    	w.unlock();// 要在写锁释放之前获取读锁
    }
} finally{
	r.unlock();
}
复制代码

实战案例

验证读读共享模式

@Test
    public void readReadMode() throws InterruptedException {
        ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock r = rw.readLock();
        ReentrantReadWriteLock.WriteLock w = rw.writeLock();

        Thread thread0 = new Thread(() -> {
            r.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 1 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                r.unlock();
            }
        },"t1");

        Thread thread1 = new Thread(() -> {
            r.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 2 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                r.unlock();
            }
        },"t2");

        thread0.start();
        thread1.start();

        thread0.join();
        thread1.join();
    }
复制代码

运行结果:

通俗易懂读写锁ReentrantReadWriteLock的使用

  • 两个线程同时运行,都获取到了读锁

验证读写互斥模式

@Test
    public void readWriteMode() throws InterruptedException {
        ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock r = rw.readLock();
        ReentrantReadWriteLock.WriteLock w = rw.writeLock();

        Thread thread0 = new Thread(() -> {
            r.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 1 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                r.unlock();
            }
        },"t1");

        Thread thread1 = new Thread(() -> {
            w.lock();
            try {
                Thread.sleep(1000);
                System.out.println("Thread 2 running " + new Date());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                w.unlock();
            }
        },"t2");

        thread0.start();
        thread1.start();

        thread0.join();
        thread1.join();
    }
复制代码

运行结果:

通俗易懂读写锁ReentrantReadWriteLock的使用

  • 两个线程间隔1秒,互斥执行

真实缓存例子

什么场景下读多写少? 想必最先想到的就是缓存把,ReentrantReadWriteLock在缓存场景中就是一个很典型的应用。

通俗易懂读写锁ReentrantReadWriteLock的使用

缓存更新时,是先清缓存还是先更新数据库?

  • 先清缓存:可能造成刚清理缓存还没有更新数据库,高并发下,其他线程直接查询了数据库过期数据到缓存中,这种情况非常严重,直接导致后续所有的请求缓存和数据库不一致。
  • 先更新据库:可能造成刚更新数据库,还没清空缓存就有线程从缓存拿到了旧数据,这种情况概率比较小,影响范围有限,只对这一次的查询结果有问题。

显而易见,通常情况下,先更新数据库,然后清空缓存。

public class GenericCachedDao {

    // 缓存对象,这里用jvm缓存
    Map<String, String> cache = new HashMap<>();
    // 读写锁
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 读取操作
    public String getData(String key) {
        // 加读锁,防止其他线程修改缓存
        readWriteLock.readLock().lock();
        try {
            String value = cache.get(key);
            // 如果缓存命中,返回
            if(value != null) {
                return value;
            }
        } finally {
            // 释放读锁
            readWriteLock.readLock().unlock();
        }

        //如果缓存没有命中,从数据库中加载
        readWriteLock.writeLock().lock();
        try {
            // 细节,为防止重复查询数据库, 再次验证
            // 因为get 方法上面部分是可能多个线程进来的, 可能已经向缓存填充了数据
            String value = cache.get(key);
            if(value == null) {
                // 这里可以改成从数据库查询
                value = "alvin";
                cache.put(key, value);
            }
            return value;
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 更新数据
    public void updateData(String key, String value) {
        // 加写锁
        readWriteLock.writeLock().lock();
        try {
            // 更新操作TODO

            // 清空缓存
            cache.remove(key);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}
复制代码
  • getData方法是读取操作,先加读锁,从缓存读取,如果没有命中,加写锁,此时其他线程就不能读取了,等写入成功后,释放读锁。
  • updateData方法是写操作,更新时加写锁,其他线程此时无法读取,然后清空缓存中的旧数据。

总结

本文讲解了ReentrantReadWriteLock读写锁常用的API, 以及通过几个demo的演示,讲解了读写锁的使用,希望对大家有帮助。文章来源地址https://www.toymoban.com/news/detail-463182.html

到了这里,关于通俗易懂读写锁ReentrantReadWriteLock的使用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • useVModel()的使用------通俗易懂

    用于父子组件共享数据, 1、父组件有个flag(Boolean)属性 2、将flag属性传递给子组件,要实现双向数据绑定 3、flag属性在子组件发生变化,父组件的flag属性一起改变 父组件app.vue 1、父组件有flag属性,使得flag和子组件uniInput双向绑定 2、使用v-model:flag进行双向绑定,v-model的默

    2024年01月15日
    浏览(40)
  • python类(class)的定义、使用、继承、应用的通俗易懂理解

    1.为什么使用类         一个类(class)可以包囊众多函数,所以一般使用类的情况下,都需要用到众多有共性的函数,把这些可能会一起调用的函数或者需要接连调用的函数归为一个类,这样需要使用的时候可以轻易的找到。         在处理不同细节时使用的类区别

    2024年02月22日
    浏览(37)
  • 【通俗易懂】如何使用GitHub上传文件,如何用git在github上传文件

      目录 创建 GitHub 仓库 使用 Git 进行操作 步骤 1:初始化本地仓库 步骤 2:切换默认分支 步骤 3:连接到远程仓库 步骤 4:获取远程更改 步骤 5:添加文件到暂存区 步骤 6:提交更改 步骤 7:尝试使用 SSH 协议 步骤 8:上传项目 在现代软件开发中,版本控制是一个至关重要的

    2024年02月12日
    浏览(46)
  • 【rpc】Dubbo和Zookeeper结合使用,它们的作用与联系(通俗易懂,一文理解)

    目录 Dubbo是什么?         把系统模块变成分布式,有哪些好处,本来能在一台机子上运行,为什么还要远程调用 Zookeeper是什么? 它们进行配合使用时,之间的关系 服务注册 服务发现 动态地址管理         Dubbo是一种开源的高性能、轻量级的分布式服务框架,它致力

    2024年02月09日
    浏览(50)
  • 深度学习:使用nanodet训练自己制作的数据集并测试模型,通俗易懂,适合小白

    关于目标检测的模型有很多, nanodet 模型小且轻量化,适合移植部署到硬件设备上,非常推荐学习这个模型。经过我自己踩了无数坑后总结了此教程,助学刚接触nanodet,想要快速上手的学者,教程很详细,学者耐心看。 我提供了一个已经调试好的源码包,后面的教程也都是

    2024年02月15日
    浏览(48)
  • 用通俗易懂的方式讲解大模型:Prompt 提示词在开发中的使用

    OpenAI 的 ChatGPT 是一种领先的人工智能模型,它以其出色的语言理解和生成能力,为我们提供了一种全新的与机器交流的方式。但不是每个问题都可以得到令人满意的答案,如果想得到你所要的回答就要构建好你的提示词 Prompt。本文将探讨 Prompt 提示词在开发中的应用和优势,

    2024年02月03日
    浏览(45)
  • 用通俗易懂的方式讲解:使用 MongoDB 和 Langchain 构建生成型AI聊天机器人

    想象一下:你收到了你梦寐以求的礼物:一台非凡的时光机,可以将你带到任何地方、任何时候。 你只有10分钟让它运行,否则它将消失。你拥有一份2000页的PDF,详细介绍了关于这台时光机的一切:它的历史、创造者、构造细节、操作指南、过去的用户,甚至还有一种回到过

    2024年01月23日
    浏览(48)
  • 用通俗易懂的方式讲解:使用 LangChain 和 LlamaIndex 从零构建PDF聊天机器人

    随着大型语言模型(LLM)(如ChatGPT和GPT-4)的兴起,现在比以往任何时候都更容易搭建智能聊天机器人,并且可以堆积如山的文档,为你的输入提供更准确的响应。 无论你是想构建个人助理、定制聊天机器人还是自动文档分析系统,本系列都将为你提供构建自己的LLM聊天机器

    2024年01月18日
    浏览(56)
  • Typescript - 通俗易懂的 interface 接口,创建接口 / 基础使用 / 可选属性 / 只读属性 / 任意属性(详细教程)

    在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。 TypeScript 中的接口是一个非常灵活的概念,除了可用于 对类的一部分行为进行抽象 以外,也常用于对「对象的形状(Shape)」进行描述。 ​TypeScript 的核心原则之一是对值所

    2023年04月15日
    浏览(50)
  • 用通俗易懂的方式讲解:使用 Mistral-7B 和 Langchain 搭建基于PDF文件的聊天机器人

    在本文中,使用LangChain、HuggingFaceEmbeddings和HuggingFace的Mistral-7B LLM创建一个简单的Python程序,可以从任何pdf文件中回答问题。 LangChain是一个在语言模型之上开发上下文感知应用程序的框架。LangChain使用带prompt和few-shot示例的LLM来提供相关响应和推理。LangChain擅长文档问答、聊天

    2024年01月24日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包