CAS和MySql乐观锁实现下单
准备
建表t_order
:
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`version` int(255) DEFAULT NULL,
`stock` int(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
初始化数据:
INSERT INTO `spboot`.`t_order` (`id`, `version`, `stock`) VALUES ('1', '1', '35');
mysql> select * from t_order;
+----+---------+-------+
| id | version | stock |
+----+---------+-------+
| 1 | 1 | 35 |
+----+---------+-------+
OrderDo.java
:
public class OrderDo {
private Integer id;
private Integer version;
private Integer stock;
//ommited getter & setter
}
OrderDoMapper.java
:
public interface OrderDoMapper {
OrderDo selectByPrimaryKey(Integer id);
int desStockByCas(@Param("orderId") int orderId,
@Param("oldStock") int oldStock, @Param("desStock") int desStock);
int desStockByOptimistic(@Param("orderId") int orderId, @Param("oldVersion") int oldVersion,
@Param("desStock") int desStock);
int desStockByLarge(@Param("orderId") int orderId,@Param("desStock") int desStock);
}
OrderManager.java
:
@Component(value = "orderManager")
public class OrderManager {
@Autowired
private OrderDoMapper orderDoMapper;
public OrderDoMapper getDao(){
return this.orderDoMapper;
}
public int desStockByCas(int orderId, int oldStock, int desStock){
return orderDoMapper.desStockByCas(orderId,oldStock, desStock);
}
public int desStockByOptimistic(int orderId, int oldVersion, int desStock){
return orderDoMapper.desStockByOptimistic(orderId, oldVersion, desStock);
}
public int desStockByLarge(int orderId, int desStock){
return orderDoMapper.desStockByLarge(orderId, desStock);
}
}
模拟业务OrderService.java
:
@Service
public class OrderService {
private static final Logger LOGGER = LoggerFactory.getLogger(OrderService.class);
@Autowired
private OrderManager orderManager;
@Transactional
public void downOrder(){
int orderId = 1;
int desStock = 1;
OrderDo orderDo = orderManager.getDao().selectByPrimaryKey(orderId);
if (orderDo.getStock() <=0 ){
LOGGER.info(Thread.currentThread().getName()+" :无库存.....");
return;
}
//CAS
// int result = orderManager.desStockByCas(orderId, orderDo.getStock(), desStock);
//乐观锁
// int result = orderManager.desStockByOptimistic(orderId, orderDo.getVersion(), desStock);
//
// //大于
int result = orderManager.desStockByLarge(orderId, desStock);
if (result > 0){
LOGGER.info(Thread.currentThread().getName()+ " 下单成功...");
}else {
LOGGER.info(Thread.currentThread().getName()+ " 下单失败...");
}
}
}
测试案例
用线程OrderRunnable.java
模拟去发起下单操作:
public class OrderRunnable implements Runnable {
private CountDownLatch latch;
private OrderService orderService;
public OrderRunnable(CountDownLatch latch, OrderService orderService) {
this.latch = latch;
this.orderService = orderService;
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
orderService.downOrder();
}
}
测试案例,用50个线程模拟50个用户去下单:
@Test
public void downOrderTest() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
for (int i=0; i < 50; i++){
Thread t = new Thread(new OrderRunable(latch, orderService));
t.start();
}
latch.countDown();
Thread.sleep(10000);
}
CAS方式
orderDoMapper.xml
:
<update id="desStockByCas">
UPDATE t_order SET stock=stock-#{desStock} WHERE id=#{orderId} AND stock=#{oldStock}
</update>
有地方说扣减库存不是幂等的,需要改成设值的方式,之后的方式也一样,如:
# newStock = oldStock-desStock;
<update id="desStockByCas">
UPDATE t_order SET stock=#{newStock} WHERE id=#{orderId} AND stock=#{oldStock}
</update>
打开注解:
int result = orderManager.desStockByCas(orderId, orderDo.getStock(), desStock);
测试结果:
2018-11-26 21:30:33 [Thread-3] INFO com.xxx.service.OrderService -Thread-3 下单成功...
2018-11-26 21:30:33 [Thread-7] INFO com.xxx.service.OrderService -Thread-7 下单失败...
2018-11-26 21:30:33 [Thread-36] INFO com.xxx.service.OrderService -Thread-36 下单失败...
2018-11-26 21:30:33 [Thread-5] INFO com.xxx.service.OrderService -Thread-5 下单失败...
2018-11-26 21:30:33 [Thread-14] INFO com.xxx.service.OrderService -Thread-14 下单失败...
2018-11-26 21:30:33 [Thread-9] INFO com.xxx.service.OrderService -Thread-9 下单失败...
2018-11-26 21:30:33 [Thread-43] INFO com.xxx.service.OrderService -Thread-43 下单失败...
2018-11-26 21:30:33 [Thread-29] INFO com.xxx.service.OrderService -Thread-29 下单成功...
2018-11-26 21:30:33 [Thread-42] INFO com.xxx.service.OrderService -Thread-42 下单失败...
2018-11-26 21:30:33 [Thread-34] INFO com.xxx.service.OrderService -Thread-34 下单成功...
2018-11-26 21:30:33 [Thread-21] INFO com.xxx.service.OrderService -Thread-21 下单失败...
2018-11-26 21:30:33 [Thread-39] INFO com.xxx.service.OrderService -Thread-39 下单成功...
2018-11-26 21:30:33 [Thread-22] INFO com.xxx.service.OrderService -Thread-22 下单失败...
2018-11-26 21:30:33 [Thread-8] INFO com.xxx.service.OrderService -Thread-8 下单成功...
2018-11-26 21:30:33 [Thread-20] INFO com.xxx.service.OrderService -Thread-20 下单失败...
// ommitted some...
查看数据库:
mysql> select * from t_order;
+----+---------+-------+
| id | version | stock |
+----+---------+-------+
| 1 | 1 | 20 |
+----+---------+-------+
可以发现,本来35个库存,现在变成了20个,也就是说成功了15个
MySql乐观锁方式
MySql乐观锁是基于CAS原理的一种实现.
<update id="desStockByOptimistic">
UPDATE t_order SET stock=stock-#{desStock}, version=version+1
WHERE id=#{orderId} AND version=#{oldVersion}
</update>
打开注解:
int result = orderManager.desStockByOptimistic(orderId, orderDo.getVersion(), desStock);
测试结果:
2018-11-26 21:34:26 [Thread-20] INFO com.xxx.service.OrderService -Thread-20 下单成功...
2018-11-26 21:34:26 [Thread-25] INFO com.xxx.service.OrderService -Thread-25 下单失败...
2018-11-26 21:34:26 [Thread-11] INFO com.xxx.service.OrderService -Thread-11 下单失败...
2018-11-26 21:34:26 [Thread-34] INFO com.xxx.service.OrderService -Thread-34 下单失败...
2018-11-26 21:34:26 [Thread-26] INFO com.xxx.service.OrderService -Thread-26 下单失败...
2018-11-26 21:34:26 [Thread-24] INFO com.xxx.service.OrderService -Thread-24 下单失败...
2018-11-26 21:34:26 [Thread-40] INFO com.xxx.service.OrderService -Thread-40 下单失败...
2018-11-26 21:34:26 [Thread-14] INFO com.xxx.service.OrderService -Thread-14 下单成功...
2018-11-26 21:34:26 [Thread-22] INFO com.xxx.service.OrderService -Thread-22 下单失败...
2018-11-26 21:34:26 [Thread-21] INFO com.xxx.service.OrderService -Thread-21 下单成功...
2018-11-26 21:34:26 [Thread-49] INFO com.xxx.service.OrderService -Thread-49 下单失败...
2018-11-26 21:34:26 [Thread-4] INFO com.xxx.service.OrderService -Thread-4 下单成功...
2018-11-26 21:34:26 [Thread-7] INFO com.xxx.service.OrderService -Thread-7 下单失败...
查看数据库:
mysql> select * from t_order;
+----+---------+-------+
| id | version | stock |
+----+---------+-------+
| 1 | 19 | 17 |
+----+---------+-------+
1 row in set
可以发现,本来35个库存,现在变成了17个,也就是说成功了18个
大于方式
<update id="desStockByLarge">
UPDATE t_order SET stock=stock-#{desStock} WHERE id=#{orderId} and stock-#{desStock}>=0
</update>
打开注解:
int result = orderManager.desStockByLarge(orderId, desStock);
测试结果:
2018-11-26 21:37:32 [Thread-3] INFO com.xxx.service.OrderService -Thread-3 下单成功...
2018-11-26 21:37:32 [Thread-5] INFO com.xxx.service.OrderService -Thread-5 下单成功...
2018-11-26 21:37:32 [Thread-47] INFO com.xxx.service.OrderService -Thread-47 下单成功...
2018-11-26 21:37:32 [Thread-23] INFO com.xxx.service.OrderService -Thread-23 下单成功...
2018-11-26 21:37:32 [Thread-16] INFO com.xxx.service.OrderService -Thread-16 下单成功...
2018-11-26 21:37:32 [Thread-45] INFO com.xxx.service.OrderService -Thread-45 下单成功...
2018-11-26 21:37:32 [Thread-52] INFO com.xxx.service.OrderService -Thread-52 下单成功...
2018-11-26 21:37:32 [Thread-49] INFO com.xxx.service.OrderService -Thread-49 下单失败...
2018-11-26 21:37:32 [Thread-11] INFO com.xxx.service.OrderService -Thread-11 下单失败...
2018-11-26 21:37:32 [Thread-31] INFO com.xxx.service.OrderService -Thread-31 下单失败...
2018-11-26 21:37:32 [Thread-6] INFO com.xxx.service.OrderService -Thread-6 :无库存.....
2018-11-26 21:37:32 [Thread-44] INFO com.xxx.service.OrderService -Thread-44 :无库存.....
2018-11-26 21:37:32 [Thread-30] INFO com.xxx.service.OrderService -Thread-30 :无库存.....
2018-11-26 21:37:32 [Thread-33] INFO com.xxx.service.OrderService -Thread-33 :无库存.....
查看数据库:
mysql> select * from t_order;
+----+---------+-------+
| id | version | stock |
+----+---------+-------+
| 1 | 1 | 0 |
+----+---------+-------+
1 row in set
可以发现,本来35个库存,现在变成了0个,也就是说全部成功