加上@Transaction之后,synchronized的同步方法不能生效?

前提说明

在电商项目中,下订单时通常会涉及多个表操作,比如商品表、订单表和订单状态表等。为了保证下订单这一操作的原子性,所以下单时需要保证这些表的操作要么同时成功要么同时失败。通常情况下我们都会在下订单方法中加上事务处理,常用的都是借助Spring来进行事务管理。

同时下单时需要进行检查库存,扣减库存这些操作。为了防止超卖问题,通常我们会在这些操作加上同步锁来保证原子操作。

本次我采用了Spring声明式事务@Transactionsynchronized标注的方法,发现在单体的架构下还是出现了商品超卖的问题。下面我们一起来模拟这个出现超卖的流程。

模拟超卖流程

1. 数据库准备

数据库准备三张表,分别时order、order_item和product。往商品表中插入一条商品的信息,设置库存为1。

# 商品表
CREATE TABLE `product` (
  `id` int(11) NOT NULL,
  `product_name` varchar(255) NOT NULL,
  `count` int(5) NOT NULL,
  `create_time` time NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

# 订单信息表
CREATE TABLE `order_item` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `create_time` time NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

# 订单状态表
CREATE TABLE `order_status` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_status` int(1) NOT NULL,
  `create_time` time NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

[图片上传失败...(image-a782ee-1619323919457)]

2. 测试程序

service层

@Service
@Slf4j
public class OrderService {
  
      /**
     * 模拟下订单方法
     * @param productId
     * @param productNum
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public synchronized Integer createOrder(Integer productId, Integer productNum) throws Exception {

        // 查询商品
        Product product = productMapper.selectByPrimaryKey(productId);

        // 购买的商品不存在
        if (product == null) {
            throw new Exception("购买商品:"+productId+"不存在");
        }

        // 获取库存数量
        Integer currentCount = product.getCount();

        // 打印出每个线程获取到的库存数
        System.out.println(Thread.currentThread().getName() + "库存数: " + currentCount);

        // 检查库存
        if (productNum > currentCount) {
            throw new Exception("商品"+productId+"仅剩"+currentCount+"件,无法购买");
        }

        // 更新库存
        productMapper.updateProductCount(productNum, product.getId());

        // 设置订单状态
        Order order = new Order();
        order.setOrderStatus(1);
        order.setCreateTime(new Date());
        orderMapper.insertSelective(order);

        // 设置订单信息
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProductId(product.getId());
        orderItem.setCreateTime(new Date());
      
        // 返回订单ID
        return order.getId();
    }
}

Contrller

@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {

    //购买商品id
    private static final int productId = 100100;
    //购买商品数量
    private static final int productNum = 1;

    @Autowired
    private OrderService orderService;

    @RequestMapping("/createOrder")
    public Integer createOrder() {
        Integer orderId = null;
        try {
           orderId = orderService.createOrder2(productId, productNum);
        } catch (Exception exception) {
            log.warn(exception.getMessage());
        }
        return orderId;
    }
}

3. 并发测试

本次使用Jmeter来模拟并发下单的流程。新建一个线程组,设置0s内同时向服务器发送5个下订单的请求。

在这里插入图片描述

结果分析

我们发现product表的商品数量为-1,order_status和order_item表均有2条订单的记录。这是典型的发生了超卖的现象。

[图片上传失败...(image-2ab974-1619323919457)]

查看后台日志发现,线程4和线程10均拿到了商品库存的数量为1,所以跳过库存检查的判断,直接跑到更新库存的方法,从而导致超卖的现象。

在这里插入图片描述
在这里插入图片描述

那么问题就来了,为什么有两个线程都拿到相同的库存呀,我明明加入synchronized同步锁的,为什么方法没有同步?

在这里插入图片描述

其实也很好理解,既然两个线程都能拿到相同的库存,那就说明同步方法已经执行完,锁也释放了,但是数据库的值还没更新,还是旧值。

这就是@Transaction的坑点所在,锁释放了,事务却没提交。并发场境下,存在多个线程拿到的数据库的旧值而不是最新值的情况,从而出现多线程安全的问题。

其实也很好理解,既然两个线程都能拿到相同的库存,那就说明同步方法已经执行完,锁也释放了,但是数据库的值还没更新,还是旧值。

这就是@Transaction的坑点所在,锁释放了,事务却没提交。并发场境下,存在多个线程拿到的数据库的旧值而不是最新值的情况,从而出现多线程安全的问题。

那怎么解决?既然是锁释放了,事务没有提交的问题。那么我们可以在同步方法中,手动提交一下这个事务就OK啦。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、前言 秒杀系统其实是一个比较复杂的设计,文章先介绍设计秒杀系统的思路脉络和设计系统的原则。后面章节再详细介绍使...
    liuliuzo阅读 5,553评论 3 85
  • 搞清楚秒杀的关键问题所在,有哪些解决办法。 知识要点架构设计原理如何最大程度分流减压如何抵挡突发大流量根据业务场景...
    与诗小睡阅读 291评论 0 0
  • 微服务架构 微服务架构是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。它的主要作用是将...
    PC_Repair阅读 9,176评论 0 4
  • 秒杀系统 秒杀系统介绍 秒杀系统相信网上已经介绍了很多了,我也不想黏贴很多定义过来了。 废话少说,秒杀系统主要应用...
    蛮三刀酱阅读 1,985评论 0 5
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,628评论 28 53