不能在 synchronized 代码块中执行长耗时 io 操作,因为锁持有时间与 io 阻塞时间被强制绑定,导致线程长时间持锁阻塞,加剧锁竞争、降低吞吐量,并可能引发线程饥饿或超时雪崩。

直接在 synchronized 代码块里做长耗时 IO 操作,是典型的性能反模式。它会让持有锁的线程长时间阻塞,其他线程只能干等,严重放大锁竞争、拖垮吞吐量,甚至引发线程饥饿或超时雪崩。
为什么不能在 synchronized 里做长 IO
核心问题在于“锁持有时间”和“IO 阻塞时间”被强行绑定:
- synchronized 锁住的是临界区逻辑,不是 IO 资源;但 IO 阻塞期间锁仍被占着,白白浪费并发能力
- synchronized 在 Java 24 前默认仍会阻塞载体线程(尤其未开启优化时),长 IO 会卡住整个平台线程
把 IO 移出同步块,只同步关键内存操作
原则:锁只保护「共享状态变更」本身,不保护「获取状态所需的外部调用」。
比如更新缓存后写 DB:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- ❌ 错误写法(锁包住整个流程):
synchronized(lock) { data = loadFromDB(); updateCache(data); saveToDB(data); } - ✅ 正确拆分:
data = loadFromDB(); // IO 提前做,无锁<br> synchronized(lock) { updateCache(data); } // 只同步 cache 更新<br> saveToDB(data); // IO 异步或另起线程,不阻塞锁
用异步 IO + 回调/CompletableFuture 解耦
将耗时 IO 放到独立线程池执行,同步块仅负责原子性地提交任务或更新状态标记:
- 用
CompletableFuture.supplyAsync(() -> doIO(), ioPool)执行 IO - 在
thenAccept中用synchronized或AtomicReference.updateAndGet安全合并结果 - 避免在回调里再嵌套长同步块——回调本身应轻量
换更合适的并发原语替代 synchronized
针对典型场景,优先选用无锁或低开销方案:
- 计数类:用
AtomicInteger、LongAdder替代synchronized++ - 状态标记:用
volatile boolean+ CAS 循环,而非锁保护布尔翻转 - 缓存更新:用
ConcurrentHashMap.computeIfAbsent,内部已做分段/乐观锁优化 - 资源初始化:用双重检查锁(DCL)+
volatile字段,避免每次访问都同步
必要时升级锁机制,但慎用重入锁
若必须动态控制 IO 后的同步行为,可考虑 ReentrantLock 配合超时与中断:
- 用
lock.tryLock(100, TimeUnit.MILLISECONDS)避免无限等待 - IO 失败时及时
unlock(),防止锁泄漏 - 不推荐直接替换为
ReentrantLock来“缓解”长 IO —— 根本问题不在锁类型,而在职责混杂
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










