集群下的线程并发安全问题(秒杀下单业务)

在上一篇我们使用了乐观锁解决超卖问题,悲观锁保证了一人一单。具体可以参考下面这篇文章https://blog.youkuaiyun.com/ppp666777/article/details/140887568icon-default.png?t=N7T8https://blog.youkuaiyun.com/ppp666777/article/details/140887568但是这都是只有一台jvm的情况,但是当有多台jvm时锁就会失效,每个jvm内部都有自己独立的堆栈,所以我们的以前的锁就会失效 

 所以我们要在jvm外部提供一个锁监视器让所有jvm都能看到,当线程一来获取锁时,获取成功后锁监视器对象会持有线程一中用户id,此时jvm中的任意线程来获取锁时会失败,会等待所释放,而线程一则正常执行业务逻辑。

 我们可以基于redis来实现这个分布式锁

我们基于set nx ex 来实现获取锁的操作

当获取锁成功后如果因为执行业务超时那么会自动释放锁

封装了获取锁和释放锁的方法

public class SimleRedisLock implements ILock{
    private String name;
    private StringRedisTemplate redisTemplate;
    public SimleRedisLock(String name, StringRedisTemplate redisTemplate) {
        this.name = name;
        this.redisTemplate = redisTemplate;
    }
    private static final String KEY_PREFIX = "lock:";
    @Override
    public boolean tryLock(long timeoutSec) {
//获取当前线程的id
        long id = Thread.currentThread().getId();
        Boolean success = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, id + "", timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }
    @Override
    public void unlock() {
    redisTemplate.delete(KEY_PREFIX + name);
    }
}

初步实现

     Long userId = UserHolder.getUser().getId();
        //创建锁对象
        SimleRedisLock lock = new SimleRedisLock("order:"+userId,redisTemplate);
        //获取锁
        boolean isLock = lock.tryLock(1200);
        if (!isLock) {
           //获取锁失败,返回错误或重试
            return Result.fail("不允许重复下单");
        }

        try {
            //只有通过spring创建的代理对象调用@Transactional注解类下的方法时
            注解才会生效,所以需要通过AopContext.currentProxy()方法来获取当前对象的代理类
            通过他调用creatVoucherOrder方法
            IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
            return proxy.creatVoucherOrder(voucherId, voucher);
        } finally {
            lock.unlock();
        }

存在的缺点 

线程一获取锁成功,但是由于业务阻塞导致锁超时释放,此时线程二来获取锁可以成功获取,当线程二开始执行时线程一正好完成业务了,此时它会释放锁,这时的锁已被线程二获取这样它就会把线程二的锁释放掉,线程三也可以来获取锁会导致并发安全问题

 改进 

在删除锁之前获取锁标识并判断是否一致

  private String name;
    private StringRedisTemplate redisTemplate;
    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true);
    public SimleRedisLock(String name, StringRedisTemplate redisTemplate) {
        this.name = name;
        this.redisTemplate = redisTemplate;
    }
    private static final String KEY_PREFIX = "lock:";
    @Override
    public boolean tryLock(long timeoutSec) {
       //获取当前线程的id
       String id = ID_PREFIX+Thread.currentThread().getId() ;
        Boolean success = redisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, id, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    } 

    public void unlock() {
       //获取线程标识
        String id = ID_PREFIX+Thread.currentThread().getId() ;
       //获取锁中的标识
        String lockId = redisTemplate.opsForValue().get(KEY_PREFIX + name);
        if (id.equals(lockId)) {
            //释放锁
           redisTemplate.delete(KEY_PREFIX + name);
        }

你以为这样就安全了嘛?显然不是。当jvm进行垃圾回收时,如果线程一此时刚好判断完成准备释放,那么会阻塞线程从而使锁超时释放,当线程二去获取锁使会成功,当阻塞结束,线程一会执行释放锁,由于判断已经完成,所以会将线程二的锁删除。也就是说判断锁和删除锁的操作必须是一个原子操作。那用什么来保证呢?其实是用Lua脚本来实现 

 

 

--比较线程标识与锁中的是否一致
if (redis.call ('get',KEYS[1] == ARGV[1])) then
  --释放锁
  return redis.call ('del',KEYS[1])
end
return 0

 Java中调用脚本

//定义并初始化脚本
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
   static {
       UNLOCK_SCRIPT = new DefaultRedisScript<>();
       UNLOCK_SCRIPT.setLocation(new  ClassPathResource("unlock.lua"));
       UNLOCK_SCRIPT.setResultType(Long.class);
   }



public void unlock() {
    redisTemplate.execute(UNLOCK_SCRIPT,
                          Collections.singletonList(KEY_PREFIX + name),
                          ID_PREFIX+Thread.currentThread().getId()
                         );
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值