Loading

[场景设计]超卖问题

如果扣减库存的操作在一台机器上

使用本地锁

JVM 锁

不和数据库交互,模拟库存扣减。并发测试 100 个线程,访问50次。

private void jvmLock() {
    lock.lock();
    try {
        goods.setInventory(goods.getInventory()-1);
        log.info(goods.getInventory().toString());
    }finally {
        lock.unlock();
    }
}

不加锁出现并发问题,因为扣减和 set 不是原子操作,多个线程几乎同时拿到变量,多次扣减其实只减了一次。

用 voliate 能解决问题吗?不能,不保证原子性。

private void mysqlLock() {
        CommerceGoods good = goodsMapper.selectOne(Wrappers.lambdaQuery(CommerceGoods.class)
                .eq(CommerceGoods::getGoodsName, "lock-test")
                .select()
        );
        good.setInventory(good.getInventory()-1);
        goodsMapper.updateById(good);
        log.info(good.getInventory().toString());
    }

毫无疑问,同样会出现超卖现象。加锁解决,这是肉眼可见的并发量和吞吐量下降。

for update 悲观锁

select * from table where name = productName for update;

直接修改

update table set inventory = (inventory - 1) where id = 1 ;

Redis 原子操作

不加锁就不会出现超卖,而且吞吐量很高。

private void redisAtomic() {
        redisTemplate.opsForValue().decrement("lock-test");
        log.info(Objects.requireNonNull(redisTemplate.opsForValue().get("lock-test")).toString());
    }

CAS 乐观锁

update table set surplus = newQuantity where id = 1 and surplus = oldQuantity ;
private void casLock() {
        int result = 0;
        while (result==0){
            CommerceGoods good = goodsMapper.selectOne(Wrappers.lambdaQuery(CommerceGoods.class)
                    .eq(CommerceGoods::getGoodsName, "lock-test")
                    .select());
            Long inventory = good.getInventory();
            good.setInventory(good.getInventory()-1);
            result = goodsMapper.update(good,Wrappers.lambdaUpdate(CommerceGoods.class)
                    .eq(CommerceGoods::getInventory,inventory));
        }
    }

如果扣减库存的操作在多台机器上

分布式锁

posted @ 2024-08-12 23:18  Duancf  阅读(22)  评论(0)    收藏  举报