如何确保Java ReentrantLock在3秒后无论如何都能释放?
解决方案:基于定时任务的自动锁释放实现
你的定时任务方案完全可行,但需要做好线程级任务管理和锁状态校验,避免非法解锁或重复解锁问题。以下是具体实现思路和代码:
核心思路
- 用
ScheduledExecutorService实现延时解锁的定时任务(替代存在线程安全隐患的Timer)。 - 通过
ThreadLocal存储每个线程对应的定时任务实例,确保不同线程的锁操作互不干扰。 - 在
m1()获取锁后立即调度3秒后的解锁任务,同时将任务存入ThreadLocal。 - 在
m2()正常解锁时,先取消当前线程的定时任务,避免后续自动解锁触发;再执行正常解锁逻辑。 - 定时任务执行前校验锁的持有状态,防止在已正常解锁的情况下执行
unlock()抛出异常。
修改后的代码实现
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; class X { private final ReentrantLock lock = new ReentrantLock(); // 单例定时任务池,按需调整线程参数 private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); // 存储当前线程的定时解锁任务 private final ThreadLocal<ScheduledFuture<?>> unlockTaskHolder = new ThreadLocal<>(); public void m1() { lock.lock(); try { // 调度3秒后执行的自动解锁任务 ScheduledFuture<?> autoUnlockTask = scheduler.schedule(() -> { // 仅当当前线程持有锁时执行解锁,避免非法操作 if (lock.isHeldByCurrentThread()) { // 处理锁重入场景:循环解锁直到持有次数为0 while (lock.getHoldCount() > 0) { lock.unlock(); } } }, 3, TimeUnit.SECONDS); unlockTaskHolder.set(autoUnlockTask); // ... 这里编写m1的计算逻辑 } finally { // 不在这里释放锁 } } public void m2() { try { // ... 这里编写m2的后续计算逻辑 } finally { // 先取消定时解锁任务(如果尚未执行) ScheduledFuture<?> task = unlockTaskHolder.get(); if (task != null && !task.isDone()) { task.cancel(false); } unlockTaskHolder.remove(); // 清理ThreadLocal中的任务实例 // 正常执行解锁逻辑,处理重入场景 if (lock.isHeldByCurrentThread()) { while (lock.getHoldCount() > 0) { lock.unlock(); } } } } // 可选:当类不再使用时关闭线程池,避免资源泄漏 public void destroy() { scheduler.shutdown(); } }
关键细节说明
- 线程池选型:
newSingleThreadScheduledExecutor()足够满足需求,单线程执行延时解锁任务,避免资源浪费。 - ThreadLocal的作用:每个线程的定时任务独立存储,不会出现线程A的任务干扰线程B锁状态的情况。
- 任务取消逻辑:
cancel(false)表示若任务尚未执行则取消,若已在执行则不中断,防止干扰正在进行的解锁操作。 - 重入场景处理:通过
getHoldCount()判断锁的持有次数,循环执行unlock(),确保完全释放锁,即使m1被多次调用(重入锁)。 - 资源泄漏防护:
m2()中调用unlockTaskHolder.remove()清理ThreadLocal,避免线程复用(如线程池场景)时的内存泄漏;类销毁时关闭线程池,确保JVM能正常退出。
替代思路(可选)
如果业务允许,建议重构代码逻辑,将m1和m2的逻辑合并到一个方法中,用标准的try-finally块确保锁释放,从根源上避免调用顺序错误的问题。但如果必须拆分方法,上述定时任务方案是最可靠的选择。
内容的提问来源于stack exchange,提问作者tarekahf
相关产品推荐
相关产品推荐

