java多线程读写同一文件极易出错,应避免共享资源竞争;正确做法是按块/行拆分任务并行读、分文件写后合并,或用filechannel手动定位写入。

Java多线程读写文件本身可行,但**直接用多个线程并发读写同一个文件极易出错**——不是数据覆盖、就是 IOException: Stream closed、或是乱序写入。真正的优化点不在“强行并发读写”,而在「合理拆分任务 + 避免共享资源竞争」。
为什么不能让多个线程同时 FileWriter 写同一个文件
因为 FileWriter(以及 FileOutputStream)默认不保证线程安全,且底层文件指针是共享的。即使加 synchronized,也只会串行化写入,还可能因缓冲区刷新时机不同导致内容错乱。
- 多个线程调用
write()时,JVM 不控制 OS 文件偏移量,写入位置不可预测 -
BufferedWriter的内部缓冲区是线程私有,但flush()后仍会竞争同一文件句柄 - 追加模式(
new FileWriter(file, true))仅保证每次write()落到末尾,但两个线程几乎同时 flush,仍可能互相截断
并行读取大文件的正确姿势:按行/按块切分 + ExecutorService
适用于日志分析、CSV 解析等场景。核心是**避免所有线程打开同一个 FileInputStream**,而是预计算偏移区间,每个线程独立打开并跳转到指定位置读取。
- 用
RandomAccessFile获取文件总长度,结合换行符位置预切分「逻辑行区间」(需处理跨块换行) - 更稳妥的做法:用
Files.lines(path)配合parallelStream()—— 它底层仍是单线程读取+多线程处理,但语义清晰、无竞态 - 示例:
Files.lines(Paths.get("data.log")) .parallel() .filter(line -> line.contains("ERROR")) .map(this::parseLog) .forEach(result -> queue.add(result));注意:这里「读」仍是单线程,「处理」并行,适合 CPU 密集型解析
安全的多线程写入方案:分文件写 + 合并,或用 FileChannel + position
若必须并发写入同一目标语义文件(如汇总结果),推荐先分线程写入临时文件,最后原子合并;追求极致性能且控制力强,才考虑 FileChannel 手动定位写入。
立即学习“Java免费学习笔记(深入)”;
- 分文件写最简单:
thread-0.out、thread-1.out… 全部完成后用Files.move()追加到主文件(或用Files.write(..., StandardOpenOption.APPEND)) - 用
FileChannel需显式指定写入位置:FileChannel channel = FileChannel.open(path, WRITE); channel.write(buffer, position); // position 必须由上层严格分配,不能靠 channel.position()
否则多个线程传入相同position就覆盖了 - 注意
FileChannel的position参数是 long,支持超大文件;但需确保 buffer 写满前不被 GC 或复用
真正容易被忽略的是:并行读写的瓶颈往往不在 Java 层,而在磁盘随机 IO。SSD 上按块并发读可能提速,但 HDD 上频繁 seek 反而更慢。先用 top 或 iostat 确认是不是磁盘在拖后腿,再决定是否上多线程。










