You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Redisson RLock解锁异常排查:IllegalMonitorStateException问题分析

Redisson RLock 偶发 IllegalMonitorStateException 问题排查

问题现象

业务高峰期偶发抛出异常:

java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: SameId thread id: DifferentThreadId

场景是用RLock实现分布式锁,逻辑为尝试获取锁(等待5秒),成功后执行业务,最后在finally块解锁;未获取锁则执行其他逻辑。

相关代码

核心业务代码

RLock lock = lockService.getLock(TOPICNAME_LOCK_PREFIX + topicPrefix);
if (lock.tryLock(5, TimeUnit.SECONDS)) {
    try {
        // 业务处理代码
    } catch (Exception e) {
       throw new ServiceException(String.format("error: %s!", e));
    } finally {
        lock.unlock();
    }
}
// 未获取锁时的处理逻辑

LockService实现

private static RedissonClient redissonClient;

private static synchronized void initClient(String url) {
    if (redissonClient == null) {
        try {
            URI uri = new URI(url);
            Config config = new Config();
            SingleServerConfig srvConfig = config.useSingleServer()
                    .setAddress(String.format("%s://%s:%s", uri.getScheme(), uri.getHost(), uri.getPort()));
            if (StringUtils.isNotBlank(uri.getUserInfo())) {
                srvConfig.setPassword(uri.getUserInfo());
            }
            config.setLockWatchdogTimeout(10000);
            redissonClient = Redisson.create(config);
        } catch (Exception e) {
            log.error(String.format("cannot init redisson client by %s", url), e);
        }
    }
}

@PostConstruct
private void init() {
    initClient(redisProperties.getUrl());
}

public RLock getLock(String key) {
    initClient(redisProperties.getUrl());
    return redissonClient.getLock(key);
}

依赖版本

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.17.7</version>
</dependency>

问题原因分析

这个异常的本质是当前线程尝试解锁一个已经不属于自己的锁,结合高峰期出现的特点,主要由以下因素共同导致:

  1. 锁过期与看门狗续期失效
    你设置了lockWatchdogTimeout=10000(10秒),调用tryLock(5, TimeUnit.SECONDS)时Redisson会启用看门狗自动续期,但高峰期业务执行时间可能超过10秒,或者Redis与应用间网络波动导致续期请求延迟/失败,锁被Redis自动释放。此时其他线程会成功获取该锁,原线程执行完业务后调用unlock(),就会抛出异常。

  2. 代码未处理锁已释放的边界情况
    finally块中直接调用lock.unlock(),没有先判断当前线程是否仍持有锁。即使锁因过期或其他原因被释放,原线程依然会执行解锁操作,触发异常。

至于你问是网络问题还是代码问题:网络波动是诱因,但代码未处理锁过期后的解锁逻辑是直接触发异常的原因。

优化方案

即使你已经用RPermitExpirableSemaphore解决了问题,针对RLock的场景可以做以下优化避免同类问题:

  • 解锁前判断线程持有状态:在finally块中先检查当前线程是否持有锁,再执行解锁:

    finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
    
  • 显式指定锁持有时间:如果业务执行时间有明确上限,调用tryLock(waitTime, leaseTime, unit)显式设置锁的持有时间,避免看门狗续期的不确定性:

    // 例如设置锁持有时间为30秒,根据实际业务调整
    if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
        // ...
    }
    
  • 优化业务性能:高峰期业务执行时间变长是根源,优化业务逻辑减少执行耗时,从根本上降低锁过期的概率。

内容的提问来源于stack exchange,提问作者ShawnChen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 00:11:04