一、情景
某件商品(库存只有100件),如何防止在秒杀活动中被超卖的问题(500的并发)
二、不加锁
@ApiOperation("下单")
@RequestMapping(value = "/createWrongOrder/{stockId}", method = RequestMethod.GET)
@ResponseBody
public String createWrongOrder(@PathVariable String stockId) {
return orderService.createWrongOrder(stockId);
}
/**
* 库存100,并发500 下同
* 结果:不正常
* 库存扣50,下单500
*/
@Override
@Transactional
public String createWrongOrder(String stockId) {
//校验库存
Stock stock = checkStock(stockId);
//扣库存
saleStock(stock);
//创建订单
return createOrder(stock);
}
private Stock checkStock(String stockId) {
Stock stock = stockDao.findById(stockId).orElseThrow(() -> new NotFoundException("商品不存在"));
if (stock.getSale() >= stock.getCount()) {
throw new BadRequestException("库存不足");
}
return stock;
}
private void saleStock(Stock stock) {
stock.setSale(stock.getSale() + 1);
stock.setUpdateTime(new Date());
stockDao.save(stock);
}
private String createOrder(Stock stock) {
StockOrder order = new StockOrder();
order.setStockId(stock.getStockId());
order.setName(stock.getName());
order.setCreateTime(new Date());
order.setUpdateTime(new Date());
StockOrder stockOrder = stockOrderDao.save(order);
return "ID:" + stockOrder.getStockOrderId() + " == Name:" + stockOrder.getName();
}
三、乐观锁
@ApiOperation("下单-乐观加锁")
@RequestMapping(value = "/createOptimisticOrder/{stockId}", method = RequestMethod.GET)
@ResponseBody
public String createOptimisticOrder(@PathVariable String stockId) {
return orderService.createOptimisticOrder(stockId);
}
/**
* 库存100,并发500 下同
* 结果:正常
* 库存扣51,下单51
*/
@Override
@Transactional
public String createOptimisticOrder(String stockId) {
//校验库存
Stock stock = checkStock(stockId);
//乐观锁扣库存
saleStockOptimistic(stock);
//创建订单
return createOrder(stock);
}
private void saleStockOptimistic(Stock stock) {
int count = stockDao.updateByOptimistic(stock.getSale() + 1, stock.getStockId(), stock.getVersion());
if (count == 0) {
throw new BadRequestException("并发更新库存失败,version不匹配");
}
}
@Modifying
@Query("update Stock set sale = ?1 ,version = version+1 where stockId = ?2 and version = ?3")
int updateByOptimistic(Integer sale, String stockId, Integer version);
四、悲观锁
@ApiOperation("下单-悲观加锁")
@RequestMapping(value = "/createPessimisticOrder/{stockId}", method = RequestMethod.GET)
@ResponseBody
public String createPessimisticOrder(@PathVariable String stockId) {
return orderService.createPessimisticOrder(stockId);
}
/**
* 库存100,并发500 下同
* 结果:正常
* 库存扣100,下单100
*/
@Override
@Transactional
public String createPessimisticOrder(String stockId) {
//悲观锁校验库存
Stock stock = checkPessimisticStock(stockId);
//扣库存
saleStock(stock);
//创建订单
return createOrder(stock);
}
private Stock checkPessimisticStock(String stockId) {
Stock stock = stockDao.findByStockIdAndPessimistic(stockId);
if (stock.getSale() >= stock.getCount()) {
throw new BadRequestException("库存不足");
}
return stock;
}
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT stock FROM Stock stock WHERE stock.stockId= ?1")
Stock findByStockIdAndPessimistic(String stockId);
五、Sync加锁失效
@ApiOperation("下单-Sync加锁失效")
@RequestMapping(value = "/createSynchronizedOrder/{stockId}", method = RequestMethod.GET)
@ResponseBody
public String createSynchronizedOrder(@PathVariable String stockId) {
return orderService.createSynchronizedOrder(stockId);
}
/**
* 库存100,并发500 下同
* 结果:不正常
* 库存扣51,下单500
*/
/**
* 由于Spring事务是通过AOP实现的,所以在 createSynchronizedOrder 方法执行之前会有开启事务,之后会有提交事务逻辑。
* 而synchronized代码块执行是在事务之内执行的,可以推断在synchronized代码块执行完时,事务还未提交,其他
* 线程进入synchronized代码块后,读取的库存数据不是最新的。
*
* @param stockId
* @return
*/
@Override
@Transactional
public synchronized String createSynchronizedOrder(String stockId) {
//校验库存
Stock stock = checkStock(stockId);
//扣库存
saleStock(stock);
//创建订单
return createOrder(stock);
}
六、Sync加锁成功
@ApiOperation("下单-Sync加锁成功")
@RequestMapping(value = "/createSynchronizedOrderV2/{stockId}", method = RequestMethod.GET)
@ResponseBody
public String createSynchronizedOrderV2(@PathVariable String stockId) {
return orderService.createSynchronizedOrderV2(stockId);
}
/**
* 库存100,并发500 下同
* 结果:正常
* 库存扣100,下单100
*/
@Override
public synchronized String createSynchronizedOrderV2(String stockId) {
return (SpringUtil.getBean(this.getClass())).createSynchronizedOrder(stockId);
}
七、源码
https://github.com/akeung/akeung_learning.git