栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > Cocos2d-x

三种基本锁,举例解决订单超卖问题

Cocos2d-x 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

三种基本锁,举例解决订单超卖问题

环境准备:复现超卖现象

1、环境基本准备:新建一个count表,写几条模拟数据,做一条库存为1的数据

 2、新建springboot项目,用mybatis-generator快速生成所需代码mybatis-generator插件实现代码自动生成_p&f°的博客-CSDN博客

3、自己在mapper层和xml文件中写一个扣减库存的方法

 //根据id找到对应商品,扣减库存
    int updateCountAfterBuyPro(@Param("id") Integer id, @Param("buyCount") Integer buyCount, @Param("updateTime") Date updateTime);


    update count
    set count = count - #{buyCount}
    where id = #{id, jdbcType=INTEGER}

4、写一个server类,模拟扣减库存,生成订单操作


@Service
public class OrderServer {

    @Resource
    private CountMapper countMapper;

//    @Transactional(rollbackFor = Exception.class)
    public void updateCount(int id, int buyCount){

        //1、扣库存
        Count selectProduct = countMapper.selectByPrimaryKey(id);
        if (selectProduct == null){
            throw new RuntimeException("找不到对应的商品");
        }

        Integer productCount = selectProduct.getCount();
        System.out.println(Thread.currentThread().getName() + "获取的库存值是:" + productCount);
        if (productCount < buyCount){
            throw new RuntimeException("库存不足无法购买");
        }

        Date date = new Date();
        countMapper.updateCountAfterBuyPro(id, productCount, date);

        //2、生成订单快照(模拟)
        //3、生成订单详情表(模拟)
    }

}

5、写一个测试类,假设多人同时访问

@SpringBootTest
class DistributeLockApplicationTests {

    @Autowired
    private OrderServer orderServer;

    @Test
    void overCountTest() throws InterruptedException {
        CountDownLatch cdl = new CountDownLatch(5);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            es.execute(()->{
                try {
                    //让线程同时到达
                    cyclicBarrier.await();
                    orderServer.updateCount(1, 1);
                    System.err.println(Thread.currentThread().getName() + "线程执行完毕");
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    cdl.countDown();
                }
            });
        }

        cdl.await();
        es.shutdown();
    }

}

6、运行程序,查看结果

可以看到,五个线程同时到达,同时获取到数据库id为1的商品的库存为1,然后都去更新库存

 然后从数据库看到库存变为-4,不符合日常逻辑,超卖现象就产生了!

一、基于 synchronized 的方法锁(最原始的锁)

1、用方法锁在 updateCount 加上一个 synchronized 关键字。

 2、将库存改为1,测试发现,当第一个线程获取到方法锁之后,其他线程只能等待线程释放锁,才能进入方法,可以解决超卖问题,数据库库存也是正常的0

题外:解决@Transactional加上事务后,还是会产生超卖问题

 3、但是,一般情况下,下单步骤,在第一步扣减完库存后,会生成订单快照和生成订单详情表,那么这个时候方法往往会加上 @Transactional 事务回滚机制。

 在加完事务回滚后,继续将库存改为1,测试发现,还是会产生超卖现象。

数据库库存字段为 -1

 分析输出结果:

找到问题,原来是线程1先获得方法锁,进入方法后,扣减库存,但是由于开启了事务,在线程1释放锁后,提交事务之前,库存还是为1。此时,线程4获得方法锁,读取到还未提交的库存数量为1。然后线程1提交完毕,库存 1-1 = 0。线程4继续执行,剩余库存 0 - 1 = -1,然后提交。其他线程因为线程1提交完就已经是0了,所以抛出异常。

4、解决方案。

分析可以发现,事务提交是在锁之外的,所以会出现这种情况。那么把事务也锁起来,在释放锁之前,确保事务已经提交即可!

所以需要使用手动控制事务,而不用spring自带的。将原来的

@Transactional(rollbackFor = Exception.class)注释掉,修改代码如下

@Service
public class OrderServer {

    @Resource
    private CountMapper countMapper;

    //平台事务管理器
    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    //事务的定义
    @Autowired
    private TransactionDefinition transactionDefinition;

//    @Transactional(rollbackFor = Exception.class)
    public synchronized void updateCount(int id, int buyCount){

        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);

        //1、扣库存
        Count selectProduct = countMapper.selectByPrimaryKey(id);
        if (selectProduct == null){
            platformTransactionManager.rollback(transaction);
            throw new RuntimeException("找不到对应的商品");
        }

        Integer productCount = selectProduct.getCount();
        System.out.println(Thread.currentThread().getName() + "获取的库存值是:" + productCount);
        if (productCount < buyCount){
            platformTransactionManager.rollback(transaction);
            throw new RuntimeException("库存不足无法购买");
        }

        Date date = new Date();
        countMapper.updateCountAfterBuyPro(id, productCount, date);

        //2、生成订单快照(模拟)
        //3、生成订单详情表(模拟)
        platformTransactionManager.commit(transaction);
    }

}

5、将数据库库存恢复为1,测试通过,数据库库存正常为0,结果如下。

 二、基于 synchronized 的块锁(最原始的锁)

对象锁:

synchronized (this){
    
}

或者

Object object = new Object();
 
synchronized (object){ 
    //业务代码 }

类锁:

//括号里写的是 本类.class
synchronized (OrderServer.class){

        }

对象锁和类锁的区别:类锁只可能有一个,而对象锁可能可以通过new创建多个,还是会产生并发执行情况。

使用快锁修改代码后结果:

@Service
public class OrderServer {

    @Resource
    private CountMapper countMapper;

    //平台事务管理器
    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    //事务的定义
    @Autowired
    private TransactionDefinition transactionDefinition;

//    @Transactional(rollbackFor = Exception.class)
    public synchronized void updateCount(int id, int buyCount){

        synchronized (OrderServer.class){
            TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
            //1、扣库存
            Count selectProduct = countMapper.selectByPrimaryKey(id);
            if (selectProduct == null){
                platformTransactionManager.rollback(transaction);
                throw new RuntimeException("找不到对应的商品");
            }

            Integer productCount = selectProduct.getCount();
            System.out.println(Thread.currentThread().getName() + "获取的库存值是:" + productCount);
            if (productCount < buyCount){
                platformTransactionManager.rollback(transaction);
                throw new RuntimeException("库存不足无法购买");
            }

            Date date = new Date();
            countMapper.updateCountAfterBuyPro(id, productCount, date);

            //2、生成订单快照(模拟)
            //3、生成订单详情表(模拟)
            platformTransactionManager.commit(transaction);
        }
        
    }

}

执行结果符合预期。

三、使用可重入锁 ReentranLock (jdk1.5以后并发包中的锁)

setver 代码修改如下:

@Service
public class OrderServer {

    @Resource
    private CountMapper countMapper;

    //平台事务管理器
    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    //事务的定义
    @Autowired
    private TransactionDefinition transactionDefinition;

//    @Transactional(rollbackFor = Exception.class)
    public synchronized void updateCount(int id, int buyCount){

        ReentrantLock lock = new ReentrantLock();

        lock.lock();
        try {
            TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
            //1、扣库存
            Count selectProduct = countMapper.selectByPrimaryKey(id);
            if (selectProduct == null){
                platformTransactionManager.rollback(transaction);
                throw new RuntimeException("找不到对应的商品");
            }

            Integer productCount = selectProduct.getCount();
            System.out.println(Thread.currentThread().getName() + "获取的库存值是:" + productCount);
            if (productCount < buyCount){
                platformTransactionManager.rollback(transaction);
                throw new RuntimeException("库存不足无法购买");
            }

            Date date = new Date();
            countMapper.updateCountAfterBuyPro(id, productCount, date);

            //2、生成订单快照(模拟)
            //3、生成订单详情表(模拟)
            platformTransactionManager.commit(transaction);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

}

 结果符合预期。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/952410.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号