Java文件IO流竞态条件问题:结果不符合预期求助
解决Java文件IO累加操作中的竞态条件问题
兄弟,太懂你这种踩坑的糟心感了!你遇到的异常值完全是竞态条件搞的鬼——你的「读最后一条数据→加1→写入」流程不是原子操作,当程序多次运行(或者多线程并发执行)时,多个操作会交叉干扰:比如A刚读到旧值还没写完,B又读了同一个旧值,最后俩都写入加1后的结果,就会出现重复值、跳变值这种乱子。
下面给你几个针对性的解决方案,根据你的场景选就行:
方案1:用文件锁(FileLock)保证跨进程/线程的原子性
如果是多个独立进程运行程序,或者多线程场景,Java NIO的FileLock能给文件加排他锁,确保同一时间只有一个操作能读写文件,从根源上避免竞态。
假设你的文件是每行存一个数(初始是0),要追加写入到1000,代码示例:
import java.io.*; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; public class FileCounter { private static final String FILE_PATH = "counter.txt"; public static void main(String[] args) { // 初始化文件,如果不存在就写入0 initFileIfNotExists(); // 模拟运行1000次 for (int i = 0; i < 1000; i++) { appendNextNumber(); } } private static void initFileIfNotExists() { File file = new File(FILE_PATH); if (!file.exists()) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { writer.write("0"); } catch (IOException e) { e.printStackTrace(); } } } private static void appendNextNumber() { try (RandomAccessFile raf = new RandomAccessFile(FILE_PATH, "rw"); FileChannel channel = raf.getChannel()) { // 加排他锁,直到释放或通道关闭 FileLock lock = channel.lock(); try { // 读取最后一行数据 String lastLine = null; raf.seek(0); String line; while ((line = raf.readLine()) != null) { lastLine = line; } int current = Integer.parseInt(lastLine); int next = current + 1; // 追加写入新值 raf.seek(raf.length()); raf.writeBytes(System.lineSeparator() + next); } finally { // 必须释放锁,避免死锁 lock.release(); } } catch (Exception e) { e.printStackTrace(); } } }
方案2:用同步块(单进程多线程场景专属)
如果只是同一个JVM内的多线程执行累加,用Java的synchronized关键字给读写逻辑加锁,确保同一时间只有一个线程能执行这个流程:
import java.io.*; public class ThreadSafeFileCounter { private static final String FILE_PATH = "counter_records.txt"; // 用类级别的对象做锁,确保所有线程共享同一把锁 private static final Object LOCK = new Object(); public static void main(String[] args) { initFileIfNotExists(); // 启动1000个线程模拟并发 for (int i = 0; i < 1000; i++) { new Thread(ThreadSafeFileCounter::appendNextNumber).start(); } } private static void initFileIfNotExists() { File file = new File(FILE_PATH); if (!file.exists()) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { writer.write("0"); } catch (IOException e) { e.printStackTrace(); } } } private static void appendNextNumber() { synchronized (LOCK) { try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH)); BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_PATH, true))) { // 读取最后一行 String lastLine = null; String line; while ((line = reader.readLine()) != null) { lastLine = line; } int current = Integer.parseInt(lastLine); int next = current + 1; writer.newLine(); writer.write(String.valueOf(next)); } catch (Exception e) { e.printStackTrace(); } } } }
⚠️ 注意:这个方案只适合同一个JVM内的多线程,多个独立进程的话同步块没用,得用方案1的文件锁。
方案3:改用数据库(更可靠的备选思路)
如果作业允许的话,用数据库的原子更新操作(比如MySQL的UPDATE counter SET value = value + 1)或者自增主键,数据库本身就提供了原子性保证,比文件操作稳定得多,尤其是高并发场景。当然如果必须用文件,前面两个方案就够了。
额外小提示
- 一定要处理文件不存在的情况,初始化写入0,避免空指针或解析错误。
- 追加写入时用
FileWriter(FILE_PATH, true)的追加模式,别覆盖之前的记录。 - 测试时可以多开几个线程/进程,验证结果是否从0连续到1000,确保竞态问题解决。
内容的提问来源于stack exchange,提问作者soZto
相关产品推荐
相关产品推荐

