outputstreamwriter 不是线程安全的,多线程共用会导致字符交错、乱码或 ioexception;因其缓冲区与底层流操作非原子,编码、换行、缓冲均由 java 层管理,无法保证跨线程一致性。

OutputStreamWriter 本身不是线程安全的,多线程直接共用同一个实例写入文件必然导致字符交错、乱码甚至 IOException(如 Stream closed)。
根本原因:缓冲区与底层流的非原子性
OutputStreamWriter 内部维护一个字符缓冲区,write() 调用先写入缓冲区,flush() 或缓冲区满时才真正委托给底层 OutputStream(如 FileOutputStream)。多个线程同时调用 write/flush,会破坏缓冲区状态和底层字节流的写入顺序。即使底层 FileOutputStream 在部分系统上对 write() 系统调用有原子性保证(通常只对小块数据),但 OutputStreamWriter 的字符编码、换行转换、缓冲管理完全由 Java 层控制,无法保证跨线程的逻辑一致性。
典型交错表现与复现方式
比如两个线程交替写入 "ABC\n" 和 "XYZ\n":
- 预期结果:
ABC\nXYZ\n或XYZ\nABC\n - 实际可能:字符级混杂如
ABXYZ\nC\n、AXBC\nYZ\n,或某次写入被截断、编码异常(如 UTF-8 多字节字符被拆开) - 常见触发条件:未显式 flush、使用默认缓冲区大小、高并发短内容写入
安全写入的三种可行方案
不推荐“加 synchronized 锁住整个 writer”,因为会严重串行化,失去并发意义。应按场景选择:
- 方案一:每个线程独占 FileWriter/OutputStreamWriter —— 适用于日志类场景,用不同文件名(如 threadId.log)或追加模式(true)+ 文件锁(FileChannel.lock)协调;
-
方案二:统一调度写入队列 —— 用 BlockingQueue
收集各线程写入内容,单个专属 I/O 线程消费并刷盘,避免竞争; - 方案三:写前序列化 + 原子落盘 —— 各线程生成完整文本块(含换行),通过 Files.write(path, bytes, StandardOpenOption.APPEND) 原子追加(底层依赖 OS 文件系统语义,多数可靠)
调试与验证建议
若已出现交错,可快速定位:
- 关闭自动 flush(构造时不传 autoFlush=true),手动 flush 后检查是否仍交错 —— 若仍发生,说明问题在 writer 层而非缓冲区刷新时机;
- 用 hexdump 或 xxd 查看文件二进制内容,确认是否出现非法 UTF-8 字节序列(如 0xC0 0x00),这表明多字节字符被撕裂;
- 临时替换为 PrintWriter + System.out,观察控制台输出是否也错乱 —— 可辅助判断是 writer 问题还是底层流问题。
不复杂但容易忽略:字符编码、换行符、缓冲策略三者叠加后,线程不安全的表现比纯字节流更隐蔽。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











