/concurrent-stock

商城库存从基础到处理高并发等对共享资源的访问问题

Primary LanguageJava

并发在很多实际生产的项目中会面临到,并不是说系统小,用户量少就不会存在并发问题。所以对并发的处理和解决思路是每一步开发必须考虑的问题;

下面我从实际项目中剥离出的一个场景做个总结。一方面加深自己的理解,另一方面可以分享给大家。

系统中有一批爆款产品,在618中年庆活动中售卖。前面页面展示产品,用户选择产品下单,即完成操作。暂且忽略支付环境

NO.1 synchronized 同步代码锁 修饰代码块

@Transactional
@Override
public synchronized ProductOrder commitProductOrder(Long productId) {
        ProductStock productStock = productStockMapper.selectById(productId);
        //库存已卖完
        if(productStock == null || productStock.getStock() == 0) {
            return null;
        }
        //假定一次只卖一件
        productStock.setStock(productStock.getStock() - 1);
        if(productStockMapper.updateById(productStock) == 0){
            //数据已过期=>重新执行售卖逻辑
            logger.error("数据已过期=>重新执行售卖逻辑,{}",productId);
            return null;
        }
        //产生订单
        ProductOrder productOrder = new ProductOrder();
        productOrder.setOrderName("成功售卖一件" + productStock.getProductName());
        productOrder.setProductId(productStock.getId());
        productOrder.setProductName(productStock.getProductName());
        productOrderMapper.insert(productOrder);
        return productOrder;
}

观察上面的代码其实没有什么问题,但是实际模拟并发就会发现,实际库存和预计的存在差别。原因如下:synchronized修饰的非静态方法,实际是通过对象锁同步各个线程执行顺序 ,但是在spring中事务管理是通过aop对方法切面时间事务的开启提交,所以通过伪代码可以发现:

transaction.begin
synchronized(object) {
 select * from product where product_id=##
 ...
}
transaction.commit

上面的代码可以发现当多个线程进入时,事务可以同时开启,然后顺序执行代码块的内容,然后同时提交,所以每个线程并不能读取到数据库最新的数据,所以存在问题;

synchronized 几种应用场景

实例对象锁

  • => synchronized(object) : 同步代码块修饰示例对象,只要同一个示例,多个线程就是串行执行,一旦obect不同,就不能保证同步
  • => synchronized(this) : 同步代码块修饰this,this实际上值当前类的一个对象,同理,当这个类实例化了多个对象,那么这个类中的this是不同的,一样不能保证同步
  • => synchronized(object) : 同步代码块修饰示例对象,只要同一个示例,多个线程就是串行执行,一旦obect不同,就不能保证同步

态类

  • => synchronized static function() : 在静态方法前修饰方法,被调用的方法,对多线程是串行执行;
  • => synchronized(Class.class) : 类锁,多线程串行执行