system.out.println() 是同步io操作,因内部用 synchronized(this) 锁住 printstream 实例,且 newline() 重复加锁,导致高并发下串行执行、锁升级及系统调用开销大,应依场景改用条件调试或专业日志框架。

System.out.println() 看似简单,实则暗藏同步开销。它不是“直出”语句,而是一个带锁、带缓冲、带刷新的完整IO操作,高并发下容易成为性能瓶颈。
它为什么是同步的?
源码直接说明问题:
- println() 方法内部用 synchronized(this) 包裹 print() 和 newLine(),锁对象是 System.out 所指向的 PrintStream 实例(即标准输出流)
- newLine() 方法自身也加了同一把锁,两次进入同一对象监视器,形成串行执行路径
- 这种设计初衷是防止多线程输出内容错乱(比如两行日志交织成一行),但代价是牺牲并发吞吐
锁带来的实际性能影响
影响程度取决于场景,但关键点很明确:
- 在单线程或低频调试时几乎无感,JVM 会启用偏向锁甚至无锁优化
- 一旦多个线程频繁调用(如循环内打印、高频请求日志),就会触发锁升级:偏向锁 → 轻量级锁 → 重量级锁
- 重量级锁涉及用户态/内核态切换,上下文切换开销显著,实测 10 万次 println 可比空循环慢上千倍
- 即使没阻塞,synchronized 也会强制内存屏障,影响指令重排序和 CPU 缓存一致性,间接拖慢整体执行
不只是锁:还有 IO 和缓冲的叠加开销
同步只是冰山一角,背后还有三层隐性成本:
- 字符编码与字符串拼接:println(Object) 会先调用 String.valueOf(),涉及对象 toString()、StringBuilder 扩容等
- 缓冲区写入与刷盘:每次 println 都触发 write() + 换行符 + flush()(默认 autoFlush=true),实际是三次系统调用准备
- 控制台终端渲染:IDE 或终端对大量输出有渲染延迟,尤其 Windows CMD 下更明显,这不是 JVM 问题,但加剧感知卡顿
替代方案不是“不用”,而是“用对”
开发中完全禁用不现实,关键是分场景处理:
- 禁止在生产代码、循环体、高频方法中出现——上线前全局 grep "System\.out\.println" 是基本规范
- 调试阶段可用,但建议配合条件开关:
if (DEBUG) System.out.println(...),避免残留 - 需要结构化、可配置、异步、分级的日志能力,必须用 Logback / Log4j2:它们支持异步 Appender、日志采样、文件滚动、JSON 格式等
- 若需极简控制台输出(如脚本工具),可考虑重定向 System.out 到带缓冲的 PrintStream,并关闭 autoFlush,手动批量 flush











