在线程池中安全释放reentrantreadwritelock,必须由加锁的同一线程在同一个runnable/callable的try-finally块内完成释放,不可跨任务、跨线程或依赖线程池回调;写锁降级也须在同一任务内严格按序执行。

在线程池中安全释放 ReentrantReadWriteLock,关键不是“能不能放”,而是“谁来放、什么时候放、在哪一层放”。锁必须由**加锁的同一线程**在**同一次执行上下文内**释放,而线程池会复用线程、切换任务,稍不注意就会出现 IllegalMonitorStateException 或锁泄漏。
必须由加锁线程在同一个 Runnable/Callable 中 unlock
线程池中的任务是独立调度的。如果某个任务获取了读锁或写锁,就必须在该任务的 try-finally 块里完成释放 —— 不能跨任务、不能委托给其他线程、也不能依赖线程池生命周期回调(如 beforeExecute)。
- 错误做法:在任务 A 中 lock(),然后 submit 另一个任务 B 来 unlock() —— 这会抛异常,因为 B 不是锁持有者
- 正确结构:所有 lock/unlock 必须成对出现在同一 try-finally 块中,且位于实际执行业务逻辑的 Runnable/Callable 内部
- 示例片段:
executor.submit(() -> { readLock.lock(); try { Object v = cache.get(key); process(v); } finally { readLock.unlock(); // ✅ 同一线程、同一栈帧释放 } });
避免在 CompletableFuture 异步链中丢失锁上下文
使用 thenApply、thenCompose 等异步方法时,后续阶段可能被调度到不同线程执行,原锁持有状态无法传递。
- 写锁尤其危险:若主线程持写锁后调用
supplyAsync(..., executor),后续 stage 一定不在原线程,不能也不应尝试 unlock() - 安全做法:把锁保护的全部逻辑封装在单个同步块内,再整体提交;或者改用无锁结构(如
ConcurrentHashMap)替代手动锁 - 若必须分阶段处理,可将锁内数据快照复制出来(如
new ArrayList(cache.values())),再交由异步线程处理,锁在快照完成后立即释放
写锁降级场景需特别注意线程一致性
锁降级(write → read)要求写锁和读锁必须由同一个线程获取与释放,且顺序严格:先 lock writeLock,再 lock readLock,最后 unlock writeLock。
- 在线程池中,只要整个降级流程都在同一个任务体内完成,就安全;但绝不能拆到多个 submit 调用中
- 常见误用:任务 A 获取写锁并修改缓存,然后 submit 任务 B 去“降级读取”——B 没有写锁,无法完成合法降级,且读锁也无意义
- 正确模式:
executor.submit(() -> { writeLock.lock(); try { cache.put(key, value); readLock.lock(); // ✅ 同一线程获取读锁 writeLock.unlock(); // ✅ 释放写锁,保留读锁 // 此时可安全读取最新状态,供后续局部计算用 } finally { readLock.unlock(); // ✅ 最终释放读锁 } });
使用 tryLock 配合超时可防死锁,但不解决释放归属问题
tryLock(long, TimeUnit) 能避免无限阻塞,但它不改变“谁加锁谁释放”的铁律。
- 即使用了
if (readLock.tryLock(1, SECONDS)) { ... } finally { if (locked) readLock.unlock(); },仍需确保 finally 块在同一线程执行 - 不要在 catch 超时异常后,把锁状态交给别的线程清理 —— 锁对象不记录“谁该还”,只认“当前持有者”
- 建议配合布尔标记 + 显式判断,防止未成功加锁却误调 unlock()
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











