PROD环境锁无限期未释放问题:代码优化请求
并发锁无限期未释放问题的修复方案
首先,咱们先拆解下你遇到的问题:测试环境一切正常,但生产环境因为数据量大、多服务器并发,出现了锁无限期未释放,线程卡在isActive()方法,还占用大量CPU和内存。结合你的代码来看,核心问题出在锁刷新回调与主线程的状态竞争,以及分布式场景下锁资源的管理逻辑缺失。
原代码的核心问题分析
stillRunning状态的竞争与可见性:虽然用了AtomicBoolean,但在多服务器分布式场景下,主线程在finally里设置stillRunning.set(false),和refreshLock的后台回调线程可能存在竞争。如果后台线程没及时感知到状态变化,isActive()会一直返回true,导致回调线程无限循环调用,占用CPU。- 锁释放的时机错误:你在
finally里直接释放锁,但此时refreshLock的后台刷新线程可能还在运行。锁被释放后,lockReleased()回调不一定会被触发,导致stillRunning一直处于true状态,锁的生命周期管理混乱。 - 缺乏主动终止回调的逻辑:
registerNode执行完成后,没有主动终止刷新回调的逻辑,完全依赖锁释放的被动回调,在分布式环境下这种依赖不可靠。
修改方案
下面是调整后的代码,我会逐点说明修改的原因:
public void registerToken(NodeRef nodeRef) throws IdenticalContentException { // 用volatile修饰,确保多线程(跨JVM场景下配合锁机制)的可见性 volatile boolean isRunning = true; String lockToken = null; String nodeRefToken = getToken(nodeRef); JobLockService.JobLockRefreshCallback callback = new JobLockService.JobLockRefreshCallback() { public void lockReleased() { isRunning = false; } public boolean isActive() { // 增加额外的安全检查:如果锁已经被释放(或者主线程已经结束),直接返回false return isRunning && (lockToken != null); } }; try { // 调整锁获取的超时参数,生产环境建议根据实际业务耗时调整 lockToken = this.jobLockService.getLock(getLock(nodeRefToken), 60000L, 5000L, 3); if (lockToken == null) { LOG.error("Failed to acquire lock for node: {}", nodeRef); throw new LockAcquisitionException("Could not acquire lock after retries"); } // 启动锁刷新后,立即执行业务逻辑 this.jobLockService.refreshLock(lockToken, getLock(nodeRefToken), 60000L, callback); // 执行业务逻辑时增加超时控制(如果registerNode是耗时操作) long startTime = System.currentTimeMillis(); if (isRunning) { registerNode(nodeRef, nodeRefToken); // 业务完成后主动终止回调 isRunning = false; } // 检查是否超时,避免业务逻辑卡死 if (System.currentTimeMillis() - startTime > 55000L) { LOG.warn("registerNode took longer than expected for node: {}", nodeRef); isRunning = false; } } catch (LockAcquisitionException lae) { LOG.error("Failed to acquire a lock for metadata checking on node: {}", nodeRef, lae); throw lae; } finally { // 主动终止回调,确保后台刷新线程停止 isRunning = false; if (lockToken != null) { try { // 释放锁时捕获异常,避免因为锁已经被释放导致的报错 this.jobLockService.releaseLock(lockToken, getLock(nodeRefToken)); } catch (Exception e) { LOG.warn("Failed to release lock for node: {}", nodeRef, e); } } } }
关键修改点说明
isRunning的可见性优化:把AtomicBoolean换成volatile变量(配合锁的分布式特性,确保跨线程/跨JVM的状态可见性),同时在isActive()里增加lockToken != null的检查,避免锁已经释放后回调还在运行。- 主动终止回调:在
registerNode执行完成后,立即设置isRunning = false,主动终止后台刷新线程的循环,避免CPU空转。 - 锁参数调整:延长锁的持有时间和重试间隔,适配生产环境的大数据量场景,同时增加锁获取失败的明确判断。
- 超时控制:给
registerNode增加耗时监控,超过阈值主动终止回调,避免业务逻辑卡死导致锁长期占用。 - 异常安全的锁释放:在释放锁时捕获异常,防止因为锁已经被自动释放(超时)导致的报错,保证
finally块能正常执行。 - 日志增强:增加节点ID的日志输出,方便生产环境排查具体是哪个节点出现锁问题。
额外建议
- 分布式锁的选型验证:确认
JobLockService是否是真正的分布式锁(比如基于Redis/ZooKeeper),如果是基于本地内存的锁,在多服务器场景下完全无效,必须替换为分布式锁实现。 - 锁粒度优化:如果
getLock(nodeRefToken)的锁粒度太粗(比如用了全局锁),会导致并发冲突加剧,建议细化锁粒度到单个节点级别。 - 监控告警:给锁的获取、释放、刷新操作增加监控指标(比如Prometheus metrics),当锁持有时间超过阈值时触发告警,提前发现问题。
内容的提问来源于stack exchange,提问作者user739115
相关产品推荐
相关产品推荐

