关键是要将耗时操作移出锁的临界区,只用锁保护真正需要原子性的共享状态更新;外部调用、计算、转换等均应放在锁外,辅以异步回调、预加载、缓存和无锁结构优化。

关键不是“能不能调”,而是“该不该在锁里调”——外部耗时方法(如数据库查询、HTTP 调用、文件读写、RPC、复杂计算)一旦进入同步块或 lock 临界区,就会把锁拖长、拖重、拖死。这直接放大锁竞争,还极易引发死锁、线程堆积甚至服务雪崩。
把耗时操作坚决移出临界区
只让锁保护真正需要原子性保障的那几行:比如更新共享变量、修改 ConcurrentHashMap 的某个桶、递增计数器。其余逻辑——参数校验、日志拼接、DTO 转换、远程调用、结果组装——全放外面。
- 错误示范:synchronized (lock) { order = db.load(id); updateCache(order); } —— 数据库查一次可能几百毫秒,锁就挂起几十个线程
- 正确做法:先查库得到 order,再进同步块仅做 cache.put(id, order) 或 version.compareAndSet(old, new)
用异步+回调替代持锁等待
如果业务逻辑确实依赖外部结果才能更新状态,就别让线程干等。改用 CompletableFuture 或消息队列解耦:
- 发起远程调用后立即释放锁,用 thenAcceptAsync 在另一个线程里处理响应并安全更新共享数据
- 或者发一条本地事件(如 Spring ApplicationEvent),监听器在无锁上下文中执行后续动作
- 避免在回调里重新抢同一把锁——容易形成隐式嵌套锁,诱发死锁
提前准备、预加载、缓存兜底
很多“不得不调外部”的场景,其实源于设计没前置。例如:
- 用户登录后,提前异步拉取其权限树并缓存,后续鉴权就不需实时查 DB
- 订单创建时,用本地缓存或默认值填充非关键字段,关键字段(如库存扣减)才走强一致锁 + DB
- 对非强一致性要求的读场景,直接用 Redis 缓存结果,完全绕过锁和 DB
用原子类或无锁结构替代锁内计算
如果耗时来自内部计算(比如 JSON 序列化、字符串拼接、循环遍历集合),更该反思是否真需要锁:
- 计数类操作优先用 LongAdder 或 AtomicInteger,比 synchronized ++ 快且不阻塞
- 共享集合尽量选 ConcurrentHashMap、CopyOnWriteArrayList,它们内部已做分段/复制优化,多数操作无需手动加锁
- 纯内存转换逻辑(如 map → dto)彻底移出临界区,它不改共享状态,不构成并发风险
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











