ITPub博客

首页 > 数据库 > 数据库开发技术 > 读写锁分离设计模式详解

读写锁分离设计模式详解

原创 数据库开发技术 作者:Finally_m 时间:2024-11-14 10:01:00 0 删除 编辑

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

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

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

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

  1. 读写锁(ReadWriteLock):

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

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

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

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

    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

    五、优缺点

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

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

    原文作者:Java架构栈;如有侵权,请联系管理员删除。

    上一篇: 没有了~
    请登录后发表评论 登录
    全部评论

    注册时间:2021-04-13

    • 博文量
      573
    • 访问量
      243719