printstream 因 synchronized 实例方法导致高并发日志时锁竞争严重,应改用 log4j2/logback 等异步日志框架,或 atomicreference+stringbuilder 聚合、files.write 批量写入等无锁方案。

Java 中 PrintStream(如 System.out、System.err)在并发高频日志打印时,确实会成为明显的同步锁瓶颈——因为它内部使用了全局的、粗粒度的 synchronized 方法保护输出操作。
为什么 PrintStream 会成为锁热点
PrintStream 的关键方法(如 println()、print()、write())全部是 synchronized 实例方法,锁对象就是该 PrintStream 自身(例如 System.out 对应一个单例对象)。这意味着:
- 所有线程调用
System.out.println("log")时,都必须排队竞争同一把锁 - 即使只是打印一行简单字符串,也要完成锁获取 → 字符编码 → 缓冲写入 → 刷新(若启用自动 flush)→ 锁释放的完整流程
- 在高并发场景下(如每秒数万次日志),大量线程阻塞在
monitor-enter上,JFR 可观测到jdk.monitor-enter持续时间远超 1ms,甚至达数十毫秒
典型表现与误判风险
开发者常误以为“只是打个日志,影响能有多大”,但实际中会出现:
- 应用吞吐量骤降,QPS 卡在几百,CPU 使用率却不高(大量线程处于 BLOCKED 状态)
- 线程堆栈中频繁出现
java.io.PrintStream.println在Object.wait或Unsafe.park上挂起 - 虚拟线程环境下问题更严重:10 万个虚拟线程争抢
System.out,几乎全部挂起,吞吐退化至平台线程水平
真正有效的替代方案
不推荐“加个缓冲再同步”或“自己包装 synchronized”,而应直接绕过 PrintStream 的同步设计:
- 生产环境禁用 System.out/err 打印日志:改用 Log4j2、Logback 等专业日志框架,它们通过异步 Appender + 无锁 RingBuffer(如 Disruptor)实现百万级日志吞吐
-
调试阶段可用 AtomicReference + StringBuilder 做轻量聚合:
例如每个线程本地拼接日志片段,由单个调度线程定期 dump 到文件,完全规避共享锁 -
极简替代:用 Files.write() 配合 StandardOpenOption.APPEND:
它底层走 NIO,不依赖 PrintStream,且可配合线程池批量写入;注意需自行处理换行和线程安全的文件路径 -
绝对避免在临界区内调用 println:
比如在 synchronized 块里打印调试信息,等于把锁持有时间人为延长几十毫秒
验证是否已缓解
优化后可通过以下方式确认效果:
- JFR 中
jdk.monitor-enter事件频率下降 95% 以上,平均耗时回归 - jstack 查看线程状态,BLOCKED 线程数趋近于 0
- 压测 QPS 恢复至预期水平,且 CPU 利用率与吞吐量呈正相关增长
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











