标准输入输出流本身非高并发源头,但system.in阻塞主线程会导致应用假死,system.out/err的同步锁在高并发下引发线程竞争与调度停滞。

标准输入输出流(System.in、System.out、System.err)本身不是高并发问题的直接源头,但它们在特定上下文中会成为**主线程阻塞甚至表观“锁死JVM”的诱因**——关键不在于流本身,而在于其底层实现与线程模型的耦合方式。
根本原因:System.in 是同步阻塞式字节流,且绑定到主线程控制台
System.in 默认是 BufferedInputStream 包装的 FileInputStream,底层直接映射操作系统标准输入句柄(如 Linux 的 stdin 文件描述符 0)。调用 System.in.read() 或 new Scanner(System.in).nextLine() 时,JVM 会发起系统调用 read(0, ...),该调用在无输入时**永久挂起当前线程**,且无法被中断(除非终端发送 EOF 或 SIGINT)。
若这段代码写在主线程(如 main() 方法末尾未退出),而高并发服务又依赖主线程维持生命周期(例如 Spring Boot 默认阻塞主线程等待 shutdown),就会出现“整个应用卡住不动”的假象——其实不是 JVM 锁死,而是主线程在等键盘输入,其他线程照常运行,但应用已失去响应入口。
System.out/err 的隐式同步开销在高并发下被放大
System.out 和 System.err 是 PrintStream 实例,默认启用自动 flush 且内部使用 synchronized 方法(如 println() 全程持有 this 锁)。在高并发日志场景中:
- 成百上千虚拟线程或平台线程同时调用
System.out.println("req: " + id),会激烈竞争同一把锁; - 锁竞争导致大量线程排队,载体线程(carrier thread)被长期占用,虚拟线程调度停滞;
- 更严重的是,若某次输出触发了底层
write()阻塞(如重定向到满载磁盘或网络 socket),整个锁持有时间剧增,形成级联阻塞。
真实案例:Spring Boot 启动后卡在 console 等待输入
常见于本地调试时误加如下代码:
public static void main(String[] args) {
SpringApplication.run(App.class, args);
System.out.println("服务启动完成,按回车继续...");
try {
System.in.read(); // ← 主线程在此阻塞!
} catch (IOException e) {
Thread.currentThread().interrupt();
}
}
此时 HTTP 服务已就绪,但主线程停在 read(),JVM 不退出,Ctrl+C 才能终止。线上环境虽不会写这行,但若用 Scanner 做配置交互、或集成旧脚本依赖 System.in,同样会复现。
规避方案:彻底隔离 I/O 与业务线程
- 禁用控制台交互逻辑:生产环境移除所有
System.in调用,改用配置文件、环境变量或 API 触发运维操作; - 替换标准输出:用 SLF4J + Logback 替代
System.out,支持异步 Appender、限流、分级输出; - 必要时解耦:若真需读取输入,新建独立守护线程处理,并设置超时(
System.in.available() > 0轮询 +Thread.sleep),避免阻塞主线程; - 虚拟线程警告:在 JDK 21+ 中,
System.in.read()会阻塞承载它的 carrier thread,导致数十万虚拟线程“集体失联”,务必避免。











