Spring AOP分布式锁:子线程致锁提前释放的追踪与禁止方案问询
解决方案:分布式锁覆盖子线程任务周期的问题
一、禁止子线程启动的方案(快速规避)
1. 静态代码检查(编译期拦截)
- 自定义规则集成到SonarQube或Checkstyle,扫描被
@LockWith注解标记的方法,检测是否存在new Thread()、ExecutorService.submit()、CompletableFuture.runAsync()这类显式启动子线程的代码,直接在编译阶段抛出警告或错误。 - 核心逻辑:匹配方法上的
@LockWith注解,遍历方法体的AST节点,识别线程创建/异步提交的语法节点,触发违规提示。
2. 切面增强(运行时拦截)
- 扩展
@LockWith的切面逻辑,在方法执行前通过ASM或Spring的MethodInvocation解析方法字节码,检查是否包含子线程启动的指令(比如invokespecial java/lang/Thread.<init>、invokeinterface java/util/concurrent/ExecutorService.submit)。 - 如果检测到违规代码,直接抛出
IllegalStateException,提示"被@LockWith标记的方法不允许启动子线程执行异步任务"。
二、追踪并支持子线程的方案(适配场景)
1. 线程上下文传递+引用计数控锁
- 用
ThreadLocal存储锁的标识和引用计数,子线程启动时复制主线程的ThreadLocal上下文,并递增对应锁的计数。 - 修改锁释放逻辑:不再在主线程结束时直接释放,而是主线程和子线程结束时都递减计数,当计数归0时才真正释放锁。
- 核心代码片段:
// 全局ThreadLocal存储锁的引用计数 private static final ThreadLocal<Map<String, AtomicInteger>> LOCK_REF_COUNT = new ThreadLocal<>(); // @LockWith切面的锁获取逻辑 @Around("@annotation(lockWith)") public Object acquireLock(ProceedingJoinPoint joinPoint) throws Throwable { String lockKey = generateLockKey(lockWith); distributedLock.lock(lockKey); // 初始化主线程的引用计数为1 Map<String, AtomicInteger> refMap = LOCK_REF_COUNT.get(); if (refMap == null) { refMap = new HashMap<>(); LOCK_REF_COUNT.set(refMap); } refMap.put(lockKey, new AtomicInteger(1)); try { return joinPoint.proceed(); } finally { // 主线程结束时递减计数,归0则释放锁 decrementAndRelease(lockKey); } } // 子线程上下文复制工具 public static void submitLockAwareTask(Runnable task) { Map<String, AtomicInteger> parentRefMap = LOCK_REF_COUNT.get(); if (parentRefMap == null) { new Thread(task).start(); return; } // 复制父线程的锁计数,并递增子线程的引用 Map<String, AtomicInteger> childRefMap = new HashMap<>(parentRefMap); childRefMap.forEach((k, v) -> v.incrementAndGet()); Runnable wrappedTask = () -> { Map<String, AtomicInteger> original = LOCK_REF_COUNT.get(); try { LOCK_REF_COUNT.set(childRefMap); task.run(); } finally { // 子线程结束时递减计数,归0则释放锁 childRefMap.forEach((k, v) -> decrementAndRelease(k)); // 恢复原线程上下文 if (original != null) { LOCK_REF_COUNT.set(original); } else { LOCK_REF_COUNT.remove(); } } }; new Thread(wrappedTask).start(); } // 计数递减与锁释放逻辑 private void decrementAndRelease(String lockKey) { Map<String, AtomicInteger> refMap = LOCK_REF_COUNT.get(); if (refMap == null) return; AtomicInteger count = refMap.get(lockKey); if (count != null && count.decrementAndGet() == 0) { distributedLock.unlock(lockKey); refMap.remove(lockKey); if (refMap.isEmpty()) { LOCK_REF_COUNT.remove(); } } }
2. 自定义线程池管理异步任务
- 实现自定义
ThreadPoolExecutor,在任务提交时自动复制主线程的锁上下文,任务执行完成后自动处理锁的引用计数递减。 - 强制要求被
@LockWith标记的方法必须使用该线程池提交异步任务,禁止直接创建线程。
3. 异步方法切面拦截
- 为所有异步方法(如被
@Async标记的方法)增加切面:如果当前线程存在@LockWith的锁上下文,则自动递增锁的引用计数,执行完成后递减计数并判断是否释放锁。
三、折中方案:锁超时+续约兜底
- 如果无法完全追踪子线程生命周期,可在获取锁时设置足够长的超时时间,同时在子线程任务中定期调用锁的续约接口(比如每隔10秒续约一次)。
- 注意:需要预估任务最大耗时,超时时间过短仍会提前释放,过长则可能导致锁被长时间占用。
内容的提问来源于stack exchange,提问作者zashto
相关产品推荐
相关产品推荐

