zoukankan      html  css  js  c++  java
  • 基于redis的分布式锁 RedissonLock解锁异常解决

    问题现象

    在并发操作的场景下(对业务接口连续请求三次),使用基于redis的分布式锁 RedissonLock解锁时抛出异常。

    问题复现代码

    public boolean testLock(Integer type) {
            RLock lock = redissonClient.getLock("testLock" + 22);
            log.info("testLock:1:" + lock.toString() + ",interrupted:" +
                    Thread.currentThread().isInterrupted() + ",hold:" +
                    lock.isHeldByCurrentThread() + ",threadId:" +
                    Thread.currentThread().getId() + ",redissonClient:{}" + redissonClient);
    
            try {
                boolean res = lock.tryLock(3, 60, TimeUnit.SECONDS);
                log.info("testLock:2:" + lock.toString() + ",interrupted:" +
                        Thread.currentThread().isInterrupted() + ",hold:"
                        + lock.isHeldByCurrentThread() + ",threadId:" + Thread.currentThread().getId()
                        + ",redissonClient:{}" + redissonClient + ",res:{}" + res);
                if (res) {
                    // 业务代    
                    Thread.sleep(4000L);
                }
            } catch (InterruptedException e) {
                log.error("分布式锁{}获取失败", lock.getName());
                throw new BusinessProcessFailException("分布式锁【" + lock.getName() + "】获取失败", ErrorCode.SYSTEM_EXCEPTION.getCode());
            } finally {
                log.info("testLock:3:" + lock.toString() + ",interrupted:" +
                        Thread.currentThread().isInterrupted() + ",hold:" +
                        lock.isHeldByCurrentThread() + ",threadId:" + Thread.currentThread().getId() + ",redissonClient:{}" + redissonClient);
                lock.unlock();
            }
            return false;
        }

    具体的异常信息如下

    java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 1f24378c-5456-4321-827a-bc0a7515ec5d thread-id: 227
    at org.redisson.RedissonLock.unlock(RedissonLock.java:366) ~[redisson-2.10.5.jar:na]

    排查过程

    业务代码中添加日志打印

    log.info("testLock:1:" + lock.toString()                                   // lock锁对象地址

    + ",interrupted:" + Thread.currentThread().isInterrupted()        // 当前线程是否中断

    + ",hold:" + lock.isHeldByCurrentThread()                            // 当前请求的线程是否是锁对象的持有者

    + ",threadId:" + Thread.currentThread().getId()                     // 线程ID

    + ",redissonClient:{}" + redissonClient)                                // redissonClient地址

    + ",res:{}" + res);                                                              // 线程加锁结果

    日志

    • testLock:1:org.redisson.RedissonLock@6132b8ee,interrupted:false,hold:false,threadId:242,redissonClient:{}org.redisson.Redisson@51627e80               
    • testLock:2:org.redisson.RedissonLock@6132b8ee,interrupted:false,hold:true,threadId:242,redissonClient:{}org.redisson.Redisson@51627e80,res:{}true
    • testLock:1:org.redisson.RedissonLock@33e44749,interrupted:false,hold:false,threadId:235,redissonClient:{}org.redisson.Redisson@51627e80
    • testLock:1:org.redisson.RedissonLock@6da98d58,interrupted:false,hold:false,threadId:244,redissonClient:{}org.redisson.Redisson@51627e80
    • testLock:2:org.redisson.RedissonLock@33e44749,interrupted:false,hold:false,threadId:235,redissonClient:{}org.redisson.Redisson@51627e80,res:{}false
    • testLock:3:org.redisson.RedissonLock@33e44749,interrupted:false,hold:false,threadId:235,redissonClient:{}org.redisson.Redisson@51627e80
    • testLock:3:org.redisson.RedissonLock@6132b8ee,interrupted:false,hold:true,threadId:242,redissonClient:{}org.redisson.Redisson@51627e80
    • testLock:2:org.redisson.RedissonLock@6da98d58,interrupted:false,hold:true,threadId:244,redissonClient:{}org.redisson.Redisson@51627e80,res:{}true
    • testLock:3:org.redisson.RedissonLock@6da98d58,interrupted:false,hold:true,threadId:244,redissonClient:{}org.redisson.Redisson@51627e80

    通过日志数据可知

    线程 242、235、244 使用的是同一个redissonClient

    线程 242、235、244 使用的lock锁对象不是同一个,242是6132b8ee,235是33e44749,244是6da98d58

    线程执行流程

    testLock:1:org.redisson.RedissonLock@6132b8ee,interrupted:false,hold:false,threadId:242,redissonClient:{}org.redisson.Redisson@51627e80                    // 线程242构建锁对象

    testLock:2:org.redisson.RedissonLock@6132b8ee,interrupted:false,hold:true,threadId:242,redissonClient:{}org.redisson.Redisson@51627e80,res:{}true      // 线程 242 加锁成功

    testLock:1:org.redisson.RedissonLock@33e44749,interrupted:false,hold:false,threadId:235,redissonClient:{}org.redisson.Redisson@51627e80                    // 线程235构建锁对象

    testLock:1:org.redisson.RedissonLock@6da98d58,interrupted:false,hold:false,threadId:244,redissonClient:{}org.redisson.Redisson@51627e80                    // 线程 244 构建锁对象

    testLock:2:org.redisson.RedissonLock@33e44749,interrupted:false,hold:false,threadId:235,redissonClient:{}org.redisson.Redisson@51627e80,res:{}false    // 线程235加锁失败

    testLock:3:org.redisson.RedissonLock@33e44749,interrupted:false,hold:false,threadId:235,redissonClient:{}org.redisson.Redisson@51627e80                   // 线程235 最终会走到finally 执行解锁,但是解锁失败(该线程并没有获取到锁)

    testLock:3:org.redisson.RedissonLock@6132b8ee,interrupted:false,hold:true,threadId:242,redissonClient:{}org.redisson.Redisson@51627e80                     // 线程 242 解锁成功

    testLock:2:org.redisson.RedissonLock@6da98d58,interrupted:false,hold:true,threadId:244,redissonClient:{}org.redisson.Redisson@51627e80,res:{}true      // 线程244加锁成功

    testLock:3:org.redisson.RedissonLock@6da98d58,interrupted:false,hold:true,threadId:244,redissonClient:{}org.redisson.Redisson@51627e80                     // 线程244解锁成功

    解决方法

    解锁时添加判断

    if (lock.isLocked() && lock.isHeldByCurrentThread()) {
    lock.unlock();
    }

    lock.isLocked():判断要解锁的key是否已被锁定。

    lock.isHeldByCurrentThread():判断要解锁的key是否被当前线程持有。

    结论

    1、只有加锁成功才需要解锁

    2、自己加的锁,自己解,不能解别人的锁

    不积跬步,无以至千里;不积小流,无以成江海。
  • 相关阅读:
    人们常说的带宽是什么意思?
    关注前端性能
    单测学习笔记
    基于 Istanbul 生成测试用例覆盖率报告
    如何做高水科研
    Human-like Controllable Image Captioning with Verb-specific Semantic Roles(具有动词语义角色的类人可控图像字幕生成)
    Netty应用程序的全部基本构建模块_netty学习笔记(2)-20210405
    异步和事件驱动_netty学习笔记(1)-20210330
    理解 cosocket(转)
    nginx lua阶段处理流程
  • 原文地址:https://www.cnblogs.com/luao/p/14633264.html
Copyright © 2011-2022 走看看