读写锁分离设计模式详解

简介: 大家好,我是V哥。在商城系统中,用户浏览商品详情页时可以查看库存数量,这是高频的读操作;当用户下单成功时,系统会更新库存数量,这是低频的写操作。为了提升系统的并发性和性能,读写锁分离设计模式是最好的选择。该模式通过将读操作和写操作分离,允许多个读线程并发执行,而写操作则需要独占锁,确保数据一致性。适用于读多写少的场景,如库存管理、缓存读取等。通过读写锁分离,可以显著提升系统的性能和用户体验。

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

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

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

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

  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

五、优缺点

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

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

相关文章
|
6月前
|
设计模式 安全 Java
【设计模式】2、设计模式分类和单例设计模式
【设计模式】2、设计模式分类和单例设计模式
57 0
|
2天前
|
设计模式 存储 缓存
12.享元模式设计思想
享元模式是一种用于性能优化的设计模式,通过共享相同或相似对象来减少内存占用。本文档详细介绍了享元模式的基础概念、实现原理、应用场景及优缺点,并通过具体例子如Integer、String、线程池和Handler等展示了其实际应用。此外,还探讨了享元模式与其他设计模式的结合使用,以及在休闲棋类和文本编辑器中的应用。适合需要优化系统性能和资源利用率的开发者参考。
23 2
|
30天前
|
设计模式 SQL 安全
01.单例模式设计思想
本文详细介绍了单例模式的设计思想及其应用。首先阐述了单例模式的基本概念、特点与定义,并探讨其适用场景与常见问题。接着深入分析了为何使用单例模式,包括处理资源访问冲突和表示全局唯一类。随后详细讲解了几种常见的单例实现方式,如饿汉式、懒汉式、双重检查锁定、静态内部类及枚举等,对比了各自优缺点。最后讨论了单例模式可能带来的问题,如对OOP不友好、隐藏依赖关系、扩展性差等,并提出了一些替代解决方案。文章内容丰富,适合希望深入了解单例模式及其应用的读者。
26 1
|
6月前
|
设计模式 监控 安全
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
97 0
|
3月前
|
设计模式
设计模式-单一职责模式
设计模式-单一职责模式
|
5月前
|
设计模式
设计模式六大原则之 接口分离原则
设计模式六大原则之 接口分离原则
|
设计模式 存储 安全
设计模式(9) -- 设计模式分类和单例模式(8种实现)
设计模式(9) -- 设计模式分类和单例模式(8种实现)
设计模式(9) -- 设计模式分类和单例模式(8种实现)
|
设计模式 Java 程序员
设计模式之原则篇
设计模式之原则篇
|
设计模式 缓存 Java
【多线程:设计模式】享元模式
【多线程:设计模式】享元模式
138 0
|
设计模式 分布式计算 Java
多线程里面的设计模式
多线程里面的设计模式
128 0