读写锁分离设计模式详解

大家好,我是 V 哥。商城系统中,用户在浏览商品详情页时可以查看库存数量,这是读操作,频率较高。当用户下单成功时,系统会更新库存数量,这是写操作,但相对较少。这是一个再常见不过的应用场景了,在这种场景下,读写锁分离设计模式就是最好的武器。

V 哥推荐:2024 最适合入门的 JAVA 课程

读写锁分离设计模式是一种多线程设计模式,适合在有读多写少的场景中使用。它通过读写操作的分离,提升了系统的并发性和性能。在这个模式中,读操作是共享的,可以同时被多个线程执行,而写操作需要独占锁,避免并发写入带来的数据不一致问题。

读写锁分离设计模式的原理

咱们再来把读写锁分离的原理先明确一下:

  1. 读写锁(ReadWriteLock)

    • 读写锁提供了两种锁:读锁和写锁。
    • 读锁是共享的,可以允许多个线程同时持有,多个线程可以并发读取数据。
    • 写锁是独占的,只有一个线程能获取写锁。写操作会阻塞所有的读写操作,确保数据一致性。
  2. 适用场景

    • 读操作远多于写操作,数据写入不频繁的场景,如缓存、配置读取、数据分析等。
    • 通过读写分离,可以避免读操作阻塞,从而提高系统的吞吐量和并发性。

特别的爱给特别的你,满足才是硬道理

咱们就拿在电商平台的商品库存管理系统来说,库存数据需要满足如下业务需求:

  1. 高并发访问:商城有大量用户会同时访问商品详情页,查询库存数量。访问这些商品库存的用户数是巨大的,因此读取库存的操作需要具备高并发能力,保证每个用户能够快速查询到最新的库存信息。

  2. 实时更新库存:当用户成功下单,库存需要相应减少。此外,可能会有后台系统进行库存更新操作,比如在补货时增加库存。因此,写操作虽然较少,但必须做到线程安全,确保更新数据的准确性,避免因并发写入导致库存错误。

  3. 数据一致性要求:为了确保库存数据的一致性,写操作必须是独占的,也就是说,只有在没有任何读或写操作时,系统才能进行写操作,从而避免多个线程同时写入导致的库存数据错误。同时,也要保证在进行写操作时,读线程不能获取到库存的中间状态,确保用户获取的是准确的库存信息。

  4. 读多写少:在实际业务中,库存查询的频率远高于更新库存的频率,绝大部分用户操作仅涉及查询商品是否有库存,而少部分用户操作涉及到更新库存,比如完成下单、取消订单、或者后台管理员进行库存调整。

  5. 性能要求:库存查询的响应速度直接影响到用户体验,特别是在大型促销活动中,大量用户同时访问某些热门商品的库存数据,因此必须保证高并发读取的性能。而写操作因为较少,不会频繁发生,能容忍一定的等待时间。

具体操作场景

回到功能业务,通常要实现的具体功能场景是这样的:

  • 库存查询:用户在浏览商品详情页时,都会查看商品库存。一个页面可能会展示多个商品的库存,因此多个用户并发查询不同商品的库存时,系统需要支持多个读线程同时读取数据,而不会互相干扰。

  • 库存更新:当用户下单购买商品时,系统需要减少相应的库存数量。这个写操作必须是独占的,以避免并发写入导致库存数量的不一致。更新操作还会在订单取消、订单失效等情况下发生。此外,后台的库存补货也是一种写操作,更新后的库存应能被用户实时查询到。

  • 促销场景:在大促活动中(如双11、黑五促销),某些商品会有大量用户访问库存。如果不进行读写锁分离,频繁的写锁会阻塞所有的读线程,导致用户体验下降。因此,系统应支持多个用户同时进行读取操作,同时保障写入的独占性,以平衡高并发和数据一致性的需求。

实施目标

有了这样的场景,采用读写锁分离设计模式来优化库存管理,可以达到以下目标:

  1. 提升读操作的并发性:允许多个用户并发查询库存,保证在读多写少的场景下库存查询的高效性。
  2. 保证写操作的独占性:避免并发写入的冲突,确保库存数据的一致性,满足商品库存管理系统的数据准确性需求。
  3. 降低读写冲突的等待时间:在写操作较少的情况下,通过读写锁分离有效降低读操作的等待时间,提升系统整体性能。

案例:商品库存管理

下面咱们来具体看一下案例实现,我们要实现一个商品库存管理,需求是这样滴:

  • 多个线程可以同时读取某商品的库存数量。
  • 只有一个线程可以更新库存,避免多个写操作造成数据不一致。

一、实现步骤

  1. 定义商品库存管理类 InventoryManager,使用 ReentrantReadWriteLock 进行读写锁分离。
  2. 创建 checkStock 方法进行库存读取操作,获取读锁。
  3. 创建 updateStock 方法进行库存更新操作,获取写锁。

以下是实现代码:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class InventoryManager {// 商品库存存储private final Map<String, Integer> inventory = new HashMap<>();// 读写锁private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();// 获取库存数量(读操作)public int checkStock(String productId) {readLock.lock();try {return inventory.getOrDefault(productId, 0);} finally {readLock.unlock();}}// 更新库存数量(写操作)public void updateStock(String productId, int quantity) {writeLock.lock();try {int currentStock = inventory.getOrDefault(productId, 0);inventory.put(productId, currentStock + quantity);System.out.println("Updated stock for product " + productId + ": " + (currentStock + quantity));} finally {writeLock.unlock();}}
}

二、测试案例

在测试案例中,模拟多个用户并发访问库存,读取库存的线程可以并发执行,而更新库存的线程会独占锁。

public class InventoryManagerTest {public static void main(String[] args) {InventoryManager inventoryManager = new InventoryManager();// 初始化库存inventoryManager.updateStock("product_1", 100);// 模拟多个线程同时读取库存for (int i = 0; i < 5; i++) {new Thread(() -> {System.out.println("Stock for product_1: " + inventoryManager.checkStock("product_1"));}).start();}// 模拟一个线程更新库存new Thread(() -> {inventoryManager.updateStock("product_1", -10);System.out.println("Stock after selling 10 units for product_1: " + inventoryManager.checkStock("product_1"));}).start();}
}

三、代码分析

  • 读操作 (checkStock):

    • 使用 readLock 加锁,只需获取读锁,不会影响其他读取线程。
    • 多个读取线程可以同时进入 checkStock 方法,提升读取并发性。
  • 写操作 (updateStock):

    • 使用 writeLock 加锁,写操作会阻塞其他读写操作。
    • 确保写入操作是独占的,防止并发写操作导致的数据不一致问题。

四、运行结果示例

输出可能如下(顺序可能有所不同):

Stock for product_1: 100
Stock for product_1: 100
Stock for product_1: 100
Stock for product_1: 100
Stock for product_1: 100
Updated stock for product product_1: 90
Stock after selling 10 units for product_1: 90

五、优缺点

  • 优点:读写锁分离允许多个读线程并发执行,大大提高了读操作的效率,适合读多写少的场景。
  • 缺点:如果写操作频繁,写锁会阻塞读操作,可能会降低系统性能,但在写安全重要程度来看,牺牲点性能是完全可以忍受的。

到这里,你是不是可以感受到读写锁分离设计模式解决了大问题了呢,并发场景下我们必须要考虑这个问题。你学沸了吗,关注威哥爱编程,一起搞不寂寞。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/14971.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于STM32单片机多路无线射频抢答器

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、资料内容 前言 随着科技的不断进步和各类竞赛活动的日益增多&#xff0c;抢答器作为一种重要的竞…

【Linux】多线程(中)

目录 一、线程互斥 1.1 互斥概念 1.2 互斥量mutex 1.3 互斥量相关API &#xff08;1&#xff09;初始化互斥量 &#xff08;2&#xff09;销毁互斥量 &#xff08;3&#xff09;互斥量加锁和解锁 1.4 互斥量原理 1.5 重入和线程安全 二、死锁 2.1 概念 2.2 造成死锁…

光控资本:什么是庄家洗盘和出货?各有什么特征?

什么是庄家洗盘和出货&#xff1f; 庄家洗盘&#xff1a;庄家洗盘是指庄家使用其筹码优势来作出一些K线图形&#xff0c;进而引起市场上的散户投资者的惊惧&#xff0c;然后洗掉散户手中的起浮筹码&#xff0c;使盘面更加安稳&#xff0c;减轻股票后期拉升压力的行为。 庄家洗…

【Vue3】知识汇总,附详细定义和源码详解,后续出微信小程序项目(5)

快速跳转&#xff1a; 我的个人博客主页&#x1f449;&#xff1a;Reuuse博客 新开专栏&#x1f449;&#xff1a;Vue3专栏 参考文献&#x1f449;&#xff1a;uniapp官网 免费图标&#x1f449;&#xff1a;阿里巴巴矢量图标库 ❀ 感谢支持&#xff01;☀ 前情提要 &#x…

5ire:开源AI工具的新纪元

抖知书老师推荐&#xff1a; 在AI技术飞速发展的今天&#xff0c;5ire作为一款开源的AI工具&#xff0c;已经悄然改变了我们对传统AI工具的认知。那些曾对AI技术抱有疑虑的人们&#xff0c;现在可以更加自信地拥抱这一变革。原本担心工作会被AI取代的忧虑逐渐平息&#xff0c;…

Unity图形学之Shader2.0矩阵变换

1.将物体坐标系 变换 到世界坐标系&#xff1a; Unity3D里面矩阵是左乘的 P(世界) M(物体到世界的变换矩阵) * P(物体) 规律&#xff1a;3D变换 首先将物体坐标系变换到世界 2.将 世界坐标 变换 到相机坐标 P(相机) M(世界到相机的变换矩阵) * P(世界) using System.Col…

网上商城系统设计与Spring Boot框架

3 系统分析 当用户确定开发一款程序时&#xff0c;是需要遵循下面的顺序进行工作&#xff0c;概括为&#xff1a;系统分析–>系统设计–>系统开发–>系统测试&#xff0c;无论这个过程是否有变更或者迭代&#xff0c;都是按照这样的顺序开展工作的。系统分析就是分析系…

JavaWeb-JSP

可以写java代码也前端代码 jsp本来就是Serclet jsp脚本 EL表达式 要将jsp获取的东西放到域中 转发到/el-demo.jsp中 jsp中用&#xffe5;{}获取域中的信息 JSTL标签 c&#xff1a;if标签 jsp中 c:forEach标签 MVC 查询所有 在service层实现 Servlet代码&#xff1a;1.创建Br…

Area-Composition模型部署指南

一、介绍 本模型可以通过输入不同的提示词&#xff0c;然后根据各部分提示词进行融合生成图片。如下图&#xff1a; 此图像包含 4 个不同的区域&#xff1a;夜晚、傍晚、白天、早晨 二、部署 环境要求&#xff1a; 最低显存&#xff1a;10G 1. 部署ComfyUI 本篇的模型部署…

经典文献阅读之--DROID-SLAM(完美的深度学习slam框架)

0. 简介 深度学习和SLAM现在结合越来越紧密了&#xff0c;但是实际上很多时候深度学习只会作为一个block放在slam系统中。而很多深度学习slam算法&#xff0c;在slam这边的性能都不是太好&#xff0c;尤其是回环和全局优化这块。因为有一些深度学习的工作就不太适合做回环检测…

【windows 下使用 tree】

windows git bash 下使用 tree 下载tree二进制文件 https://gnuwin32.sourceforge.net/packages/tree.htm 解压缩找到 tree.exe 扔进git bash的命令目录 C:\Program Files\Git\usr\bin 打开测试

GxtWaitCursor:Qt下基于RAII的鼠标等待光标类

有时我们需要以阻塞的方式执行一点耗时的操作&#xff0c;这时需要主窗口光标呈现忙状态&#xff0c;GxtWaitCursor正是为此设计&#xff1b;重载的构造函数&#xff0c;可以让光标呈现忙状态一定时间后自动恢复。 GxtWaitCursor.h #pragma once#include <QObject>// // …

通过Python,Tkinter,文本文件,Openpyxl。实现【图书馆管理系统实现技术】

图书馆管理系统 目录 项目概述类定义 -Book类 -Library类书籍管理功能 -添加书籍 -查找书籍 -借阅书籍 -归还书籍 -列出所有书籍数据持久化 -保存书籍 -加载书籍操作日志记录图形用户界面(GUI) -界面设计 -功能实现代码原理总结实现界面 ![](https://i-blog.csdnimg.cn/dire…

飞牛私有云访问外网

飞牛私有云 fnOS NAS 是一款有着卓越的性能以及强大的兼容性和智能化的管理界面&#xff0c;它之所以能在 NAS 市场中脱颖而出&#xff0c;是因为 fnOS 基于最新的 Linux 内核&#xff08;Debian发行版&#xff09;深度开发&#xff0c;不仅兼容主流 x86 硬件&#xff0c;还支持…

HTML之表单学习记录

如果一个页面仅仅供用户浏览&#xff0c;那就是静态页面。如果这个页面还能实现与服务器进行数据交互&#xff08;像注册登录、话费充值、评论交流&#xff09;​&#xff0c;那就是动态页面。表单是我们接触动态页面的第一步。其中表单最重要的作用就是&#xff1a;在浏览器端…

redis 原理篇 30 redis内存回收 过期key处理

三十分&#xff0c;又是一个长视频&#xff0c;挺好&#xff0c;但是从标题来看&#xff0c;内容应该很简单&#xff0c;或者说&#xff0c;是他能讲简单的类型&#xff0c;本来还想再搞一篇&#xff0c;但是三十分钟的话&#xff0c;五点五十了&#xff0c;算了&#xff0c;下…

【STM32F1】——无线收发模块RF200与串口通信

【STM32F1】——无线收发模块RF200与串口通信 一、简介 本篇主要对调试无线收发模块RF200的过程进行总结,实现了以下功能。 串口普通收发:使用STM32F103C8T6的USART2串口接收中断,实现两个无线收发模块RF200间的通信。二、RF200介绍 电压:3.4-5.5V工作频率:418~455MHz发…

【MySQL从入门到放弃】InnoDB磁盘结构(二)

前言 前面我们解析了InnoDB磁盘结构中的表空间、数据字典、双写缓冲区。 本文我们继续探究磁盘结构中剩余的几个核心组件:重做日志(redo log)、撤销日志(undo log)、二进制日志(binlog) 一、重做日志 ( redo log ) WAL(Write-Ahead Logging)机制 WAL 的全称是…

Python 绘图工具详解:使用 Matplotlib、Seaborn 和 Pyecharts 绘制散点图

目录 数据可视化1.使用 matplotlib 库matplotlib 库 2 .使用 seaborn 库seaborn 库 3 .使用 pyecharts库pyecharts库 注意1. 确保安装了所有必要的库2. 检查Jupyter Notebook的版本3. 使用render()方法保存为HTML文件4. 使用IFrame在Notebook中显示HTML文件5. 检查是否有其他输…

用vscode编写verilog时,如何有信号定义提示、信号定义跳转(go to definition)、模块跳转这些功能

&#xff08;一&#xff09;安装插件SystemVerilog - Language Support 安装一个vscode插件即可&#xff0c;插件叫SystemVerilog - Language Support。虽然说另一个插件“Verilog-HDL/SystemVerilog/Bluespec SystemVerilog”也有信号提示及定义跳转功能&#xff0c;但它只能提…