You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:04:26