美文网首页
Redisson 分布式锁的正确使用

Redisson 分布式锁的正确使用

作者: 冯文议 | 来源:发表于2022-10-07 00:37 被阅读0次

    背景介绍

    前段时间,在写公司的一个项目的时候,用到了分布式锁,一个同事告诉我说,分布式锁解锁在高并发的时候会报错。

    下面看下模拟代码:

    01-simulate-lock.png

    这里锁的时间是 5 秒,而业务执行的时间是 20 秒。这里模拟的是锁的时间少于业务执行的时间。

    第二次执行的时候,就会报错,如下:

    java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 660a38bb-c50b-4117-8ee2-67da7b4303c6 thread-id: 62

    错误分析

    通过这个错误信息,也知道该如何解决这个问题,我们只需要判断是当前线程再去解锁,就不会报错的。

    可是为什么会报错呢?

    所以,我们需要先去搞清楚具体的执行流程。

    02-simluate-lock-marker.png

    最开始,我以为,如果被锁住,运行到 ① 就会被返回,后面经过测试,实际上是会走到第 ② 步,尝试获取不到锁,就会返回,在返回之前呢,会执行 finally 的代码,因为 redisson 对锁有续租的功能,所以,这时候锁还是锁住的,解锁就会报错,也就是第 ③ 步。

    实际上,我们的想法的,如果业务执行出错,我们在 finally 进行解锁,以防止程序死锁。

    显然这样写代码,不是我们所期望的,并且代码也有问题。

    优化代码

    @RequestMapping("/try-lock")
    public String tryLock() {
        
        RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:try-lock");
        if (Objects.isNull(rLock)) {
            return "lock exception";
        }
        
        boolean tryLock;
        try {
            tryLock = rLock.tryLock(3, 60, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            return "get lock exception";
        }
        if (!tryLock) {
            return "get lock failed";
        }
        
        try {
            TimeUnit.SECONDS.sleep(20);
            return "success";
        } catch (Exception e) {
            return "business exception";
        } finally {
            if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }
    }
    

    以上,便是优化后的代码,我们来一起分析一下。

    分布式加锁主要分为三步。

    第一步,主要是获取 RLock 对象,并且我们对它做了判空。

    RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:try-lock");
    if (Objects.isNull(rLock)) {
        return "lock exception";
    }
    

    第二步,尝试加锁,加锁失败,返回加锁失败。

    boolean tryLock;
    try {
        tryLock = rLock.tryLock(3, 60, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        return "get lock exception";
    }
    if (!tryLock) {
        return "get lock failed";
    }
    

    这里我们用的是 tryLock,第一个参数 waitTime,意思是等待 5 秒,如果还没获取到,就不再等待。第二个参数是 leaseTime,意思是锁的释放时间。

    第三步,就是我们业务代码。

    try {
        TimeUnit.SECONDS.sleep(20);
        return "success";
    } catch (Exception e) {
        return "business exception";
    } finally {
        if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
            rLock.unlock();
        }
    }
    

    在 finally 里,我们做锁的释放操作,在释放之前,我们对锁的状态和是否是当前线程做了判断。

    OK,如果你在实际的业务中如果遇到什么问题,欢迎留言探讨。

    引申分析

      1. lock 和 tryLock 区别?

    简单来说,lock 会一直阻塞,而 tryLock 加锁失败,会返回 false。

      1. 如果锁的时间少于业务的时间,会怎么样?

    通过上面的分析,我们知道 tryLock 会加锁失败,而 lock,在锁到释放时间后,即便业务没有执行完,也会继续执行,并且不会报错。

    @RequestMapping("/lock")
    public String lock() {
        RLock rLock = redissonClient.getLock("demo-spring-boot-redisson:lock");
        if (Objects.isNull(rLock)) {
            return "exception";
        }
        try {
            rLock.lock(5, TimeUnit.SECONDS);
            System.out.println("execute business");
            TimeUnit.SECONDS.sleep(20);
            return "success";
        } catch (Exception e) {
            return "lock exception";
        } finally {
            if (rLock.isLocked()) {
                rLock.unlock();
            }
        }
    }
    
      1. 在解锁的时候,不判断锁的状态,会报错吗,反正都会解锁?

    tryLock 不会。

    而 lock 会报错,报错信息如下:

    java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 8b7b0374-506b-442f-9bc4-9e1c1cbf4d46 thread-id: 61

    相关文章

      网友评论

          本文标题:Redisson 分布式锁的正确使用

          本文链接:https://www.haomeiwen.com/subject/oeivartx.html