真正安全、可维护的重构需分三步:理解同步语义→拆解锁粒度→引入显式锁控制;重点是理清并发边界,而非简单替换语法。

直接替换 synchronized 为 ReentrantLock 不是重构,而是埋雷。真正安全、可维护的重构需要分三步走:理解同步语义 → 拆解锁粒度 → 引入显式锁控制。重点不是“换语法”,而是借机理清并发边界。
先确认 synchronized 块的真实作用
很多老代码里 synchronized(this) 或 synchronized(Obj.class) 是“保险式写法”,实际可能只有一两行关键逻辑需要互斥。盲目换成 ReentrantLock 反而扩大临界区或遗漏异常路径。
- 用 IDE 的“Find Usages”看同一把锁被多少方法共用,是否存在读写混用、长耗时操作(如 IO、RPC)卡在锁内
- 检查是否依赖
synchronized的内存语义(如 volatile 效果),ReentrantLock的lock()/unlock()同样提供 happens-before 保证,但必须成对出现 - 留意
wait()/notify()——ReentrantLock需配合Condition重写,不能直接删掉
用 try-finally 或 try-with-resources 确保 unlock
synchronized 天然支持异常安全,但 ReentrantLock 必须手动释放。老项目往往缺少单元测试,一旦漏写 unlock() 就会死锁。
- 强制使用
try-finally模板:
lock.lock();
try { /* 业务逻辑 */ }
finally { lock.unlock(); } - 更稳妥的方式是封装工具类,提供
withLock(Runnable)或withLock(Supplier<t>)</t>方法,把加锁/解锁逻辑收口 - 避免在循环里反复
lock/unlock,也别在 if 分支外提前unlock()
按场景选锁策略,别一股脑全换
不是所有 synchronized 都值得换。优先改造以下三类:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
-
需要超时获取锁:比如防止某个服务调用阻塞整个线程池,用
lock.tryLock(3, TimeUnit.SECONDS) -
需响应中断:例如后台任务被 cancel 时应快速退出,用
lock.lockInterruptibly() -
读多写少且允许并发读:考虑升级为
ReentrantReadWriteLock,比单纯换ReentrantLock收益更大
其余简单、短小、无特殊需求的同步块,保留 synchronized 更简洁可靠——JVM 对它的优化(如锁消除、偏向锁)通常比手写锁更好。
上线前必须验证的三件事
重构后不测并发 = 白忙活。尤其老项目常有隐藏的锁顺序依赖。
- 用 JMH 写对比压测,确认吞吐和延迟没倒退;注意开启 -XX:+UseBiasedLocking 等参数保持环境一致
- 用
jstack抓现场线程栈,检查是否有线程长时间 WAITING 或 BLOCKED 在新锁上 - 在预发环境打开
-Djdk.internal.vm.debug=true(或用 Arthas watch)监控锁争用次数,突增说明粒度不合理

















