Fork me on Gitee

分布式锁——JVM锁、MySQL锁解决多线程下并发争抢资源

分布式锁——JVM锁、MySQL锁解决库存超卖问题

引入库存扣案例

需求背景

电商项目中,用户购买商品后,会对商品的库存进行扣减。

需求实现

根据用户购买商品及商品数量,对商品的库存进行指定数量的扣减

public String deductStock(Long goodsId,Integer count){
        // 1.查询商品库存的数量
        Integer stock = stockDao.selectStockByGoodsId(goodsId);
        // 2.判断库存数量是否足够
        if(stock<count){
            return "库存不足";
        }

        //3. 如果库存数量足够,那么就去扣减库存
        stockDao.updateStockByGoodsId(goodsId,stock-count);
        return "库存扣减成功";
    }

使用JMeter进行压测,会产生无法扣减完库存

使用JVM锁

使用Synchronized

public synchronized String deductStock(Long goodsId,Integer count){
        // 1.查询商品库存的数量
        Integer stock = stockDao.selectStockByGoodsId(goodsId);
        // 2.判断库存数量是否足够
        if(stock<count){
            return "库存不足";
        }

        //3. 如果库存数量足够,那么就去扣减库存
        stockDao.updateStockByGoodsId(goodsId,stock-count);
        return "库存扣减成功";
    }

使用 ReentrantLock

 @Autowired
    private StockDao stockDao;
    ReentrantLock lock =new ReentrantLock();
    public String deductStock(Long goodsId,Integer count){
        lock.lock();
        try{
            // 1.查询商品库存的数量
            Integer stock = stockDao.selectStockByGoodsId(goodsId);
            // 2.判断库存数量是否足够
            if(stock<count){
                return "库存不足";
            }

            //3. 如果库存数量足够,那么就去扣减库存
            stockDao.updateStockByGoodsId(goodsId,stock-count);
        }finally {
            lock.unlock();
        }

        return "库存扣减成功";
    }

JVM失效场景有哪些

  • 多例模式下锁会失效

    image-20240309120437804

  • 事务模式,加锁在事务之前,否则事务扣减库存还未提交,另一个线程又获取到锁。

    • 可以将synchronized加到controller。解决该事务模式下问题

image-20240309183239789

image-20240309183305237

  • 集群模式:类似与独立模式下,当多个JVM时,无法用Synchronized锁住。

MySQL锁特性解决库存超卖问题

MySQL悲观锁—单条update语句问题

  • 易造成锁范围过大。
  • 无法在程序中获取扣减库存之前的库存值,比如将之前的库存数量进行打印和发送其他系统
  • 很多场景下无法满足其他业务诉求

image-20240309182644528

image-20240309182557071

MySQL forUpdate语句

  • 易造成锁范围过大
  • 性能较差
  • 思索问题
  • select for update 和普通 select语句读取内容不一致

image-20240309182624941

image-20240309182808973

MySQL乐观锁

MySQL乐观锁实现——版本号

实现方式

  • 给数据库表增加一列“version”
  • 读取数据的时候,将“version”字段一并读出
  • 数据每更新一次,“version”字段加1
  • 提交更新时,判断库中的“version”字段值和之前取出来的“version”比较
  • 相同更新,不相同重试

MySQL乐观锁实现——时间戳

实现方式

  • 给数据库表增加一列“timestamp”
  • 读取数据的时候,将“timestamp”字段一并读出
  • 数据每次更新一次,“timestamp”字段取出当前时间戳
  • 提交更新时,判断库中的“timestamp”字段值和之前取出来的“timestamp”比较
  • 相同更新,不相同重试

举例代码实现

Dao层代码

image-20240309182008647

Service层代码,需要一定的自旋(只有当version值匹配到才终止)

image-20240309182206866

MySQL乐观锁—问题

  • 高并发写操作下性能低,适应于读多写少的场景
  • 存在ABA问题
posted @ 2024-03-10 20:57  shine-rainbow  阅读(30)  评论(0编辑  收藏  举报