对于秒杀水果,一个用户只能秒杀一种水果的一单。
业务流程
并发问题
正常
并发问题
加锁保证线程安全
判断订单是否存在,为了保证线程安全问题,我们可以加锁,synchronized。
如果加在方法上,是对this加锁,也就是说,所有的用户进来都要加锁,这样不符合我们一人一单的预期。
我们以用户ID加锁,减少锁的范围,提升性能。
userId.toString().intern()
保证唯一【返回字符串对象的规范表示】
@Transactional
public Result createVoucherOrder(Long voucherId) {
// 一人一单【前文配置的过滤器中,如果登录的用户,会在ThreadLocal中添加用户信息】
Long userId = UserHolder.getUser().getId();
// 对执行代码块加锁,intern()方法避免资源浪费
synchronized (userId.toString().intern()) {
// 查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 判断是否存在
if (count > 0) {
// 用户已经购买过了
return Result.fail("用户已经购买过一次!");
}
// 扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1") // set stock = stock - 1
.eq("voucher_id", voucherId).gt("stock", 0) // where id = ? and stock > 0
.update();
if (!success) {
// 扣减失败
return Result.fail("库存不足!");
}
集群问题
如果在集群的环境下,会有多个JVM存在,会存在多个锁监视器,这样加锁就会完全失效,还是会产生线程安全问题,所以,下一篇文章我们使用redis实现分布式锁来保证线程安全。