在集群的模式下,有多个jvm,每个jvm内部有他自己的锁,导致并行执行存在线程安全问题
分布式锁:满足分布式系统或集群模式下多线程可见并且互斥的锁
基于Redis实现分布式锁
基于redis锁的初级版本
public interface ILock {
/**
* 尝试获取锁
* @param secnodTime 锁特有的超时时间过期会自动释放
* @return
*/
public boolean tryLock(long secnodTime);
/**
* 释放锁
*/
public void unlock();
}
public class SimpleRedisLock implements ILock{
private String name;
private StringRedisTemplate stringRedisTemplate;
private static final String KEY_PREFIX="lock:";
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean tryLock(long secnodTime) {
//获取当前线程
long threadId = Thread.currentThread().getId();
//写入Redis 加入过期时间防止宕机发生在写入时间和过期时间之中
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", secnodTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(flag);
}
@Override
public void unlock() {
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
@Autowiredprivate ISeckillVoucherService seckillVoucherService;@Autowiredprivate RedisIdWorked redisIdWorked;@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 限时优惠卷的秒杀* @param voucherId* @return*/@Overridepublic Result seckillVoucher(Long voucherId) {//1.查询优惠卷SeckillVoucher voucher = seckillVoucherService.getById(voucherId);if(voucher==null){//该卷不存在return Result.fail("秒杀卷不存在");}//2.判断在秒杀开始前//得到秒杀开始时间LocalDateTime beginTime = voucher.getBeginTime();if (beginTime.isAfter(LocalDateTime.now())){//活动尚未开始return Result.ok("活动尚未开始");}//3.判断在秒杀开始后LocalDateTime endTime = voucher.getEndTime();if(endTime.isBefore(LocalDateTime.now())){return Result.ok("活动已经结束");}//4.判断库存是否充足Integer stock = voucher.getStock();if(stock<1){return Result.fail("库存不足");}UserDTO user = UserHolder.getUser();Long id = user.getId();SimpleRedisLock lock = new SimpleRedisLock("order:"+id,stringRedisTemplate);//尝试获取锁boolean flag = lock.tryLock(1200L);if(!flag){return Result.fail("一个人只能下一单");}try {//如果这个类本身调用是不具备管理事务的,如果使用Spring管理可以控制事务的一致性//获取一个spring的代理对象IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();//利用spring代理对象确保事务的一致性return proxy.createVoucherOrder(voucherId);} finally {//释放锁lock.unlock();}}@Transactionalpublic Result createVoucherOrder(Long voucherId){//5.保证一人一单//5.1用户idUserDTO user = UserHolder.getUser();Long id = user.getId();Integer count = query().eq("user_id", id).eq("voucher_id", voucherId).count();if(count>0){return Result.fail("该用户已经购买");}//6.扣减库存seckillVoucherService.update().setSql("stock=stock-1").eq("voucher_id",voucherId).gt("stock",0)//stock>0.update();//7.创建订单VoucherOrder voucherOrder = new VoucherOrder();//7.1订单idlong order = redisIdWorked.nextId("order");voucherOrder.setId(order);//7.2用户idvoucherOrder.setUserId(id);//7.3购买代金卷的idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);//8.返回订单idreturn Result.ok(order);}
分布式锁的误删问题
线程1获取了锁但业务发生阻塞导致,锁被超时释放带来的问题
改进的点是 添加线程标识做出相应的判断,判断是不是自己的锁,是就释放
public class SimpleRedisLock implements ILock{
private String name;
private StringRedisTemplate stringRedisTemplate;
private static final String KEY_PREFIX="lock:";
//使用UUID来区分不同的JVM
private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";
public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
this.name = name;
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean tryLock(long secnodTime) {
//获取当前线程
String threadId =ID_PREFIX +Thread.currentThread().getId();
//写入Redis 加入过期时间防止宕机发生在写入时间和过期时间之中
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, secnodTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(flag);
}
@Override
public void unlock() {
//获取当前线程标识
String threadId =ID_PREFIX +Thread.currentThread().getId();
//获取redis中的线程标识然后做比较
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
if(id.equals(threadId)){
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
}
判断锁和释放锁是两个动作,他们两个之间产生了阻塞导致产生问题,为了避免这个问题必须使这两个动作为原子性一起执行不能产生间隔