【黑马点评-3秒杀优惠券】四、分布式锁toy 解决一人一单
1 单体synchronized的缺陷
synchronized是JVM内部锁
两者都进入了锁的内部,这个synchronized锁形同虚设,这是由于synchronized是本地锁,只能提供线程级别的同步,每个JVM中都有一把synchronized锁,不能跨 JVM 进行上锁,当一个线程进入被 synchronized 关键字修饰的方法或代码块时,它会尝试获取对象的内置锁(也称为监视器锁)。如果该锁没有被其他线程占用,则当前线程获得锁,可以继续执行代码;否则,当前线程将进入阻塞状态,直到获取到锁为止。
而现在我们是创建了两个节点,也就意味着有两个JVM,所以synchronized会失效!
改进:分布式锁
- 前面sychronized锁失效的原因是由于每一个JVM都有一个独立的锁监视器,用于监视当前JVM中的sychronized锁,
- 所以无法保障多个集群下只有一个线程访问一个代码块。所以我们直接将使用一个分布锁,
2 分布式锁解决一人一单问题
1.工具package内 定义ILock锁接口
package com.hmdp.utils;
public interface ILock {
boolean tryLock(long timeoutSec);
void unLock();
}
2. 在SimpleRedisLock.java 实现 分布式锁的逻辑。
2.1 锁属性包括 锁name+ spring类StringRedisTemplate
2.2 tryLock方法
- 获取线程id
- redis的key为锁name
- redis的value为线程ID
2.3 unLock方法
让redis叫做锁name的key过期。
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
public class SimpleRedisLock implements ILock {
private final StringRedisTemplate stringRedisTemplate;
private final String name;
public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
this.stringRedisTemplate = stringRedisTemplate;
this.name = name;
}
@Override
public boolean tryLock(long timeoutSec) {
String id = Thread.currentThread().getId() + "";
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("lock:" + name, id, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
@Override
public void unLock() {
stringRedisTemplate.delete("lock:" + name);
}
}
3. 使用分布式锁
- 根据传入的voucherId,查询秒杀券
- 判断秒杀券是否合法
- 与MySql交互,判断开始时间,结束时间和当前时间的对比
- 判断Stock库存是否<1
- 从TheradLocal中取出用户usrId
- 调用分布式锁加锁,加锁,锁名为usrId, 并设置一个过期时间
- try前面的create订单操作,finnaly释放锁。
/**
* 用Redis做分布式锁,前置判断合法与之前一样。
*
* @param voucherId
* @return
*/
@Transactional
public Result seckillVoucherRedis(Long voucherId) {
// 1. 查询秒杀券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2. 判断秒杀券是否合法
if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
// 秒杀还没开始
return Result.fail("秒杀还没开始");
}
if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
return Result.fail("秒杀已经结束");
}
if (voucher.getStock() < 1) {
// 没券了
return Result.fail("秒杀券已抢空");
}
// 3. 使用分布式锁创建订单
Long userId = UserHolder.getUser().getId();
SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
boolean isLock = lock.tryLock(1200);
// 获取锁失败
if (!isLock) {
return Result.fail("一人一单");
}
try {
// 获取锁成功,创建代理对象,使用代理对象调用第三方事务方法
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(userId, voucherId);
} finally {
lock.unLock();
}
}
注意点
try...finally...确保发生异常时锁能够释放,注意这给地方不要使用catch,A事务方法内部调用B事务方法,A事务方法不能够直接catch,否则会导致事务失效

浙公网安备 33010602011771号