Java多线程如何将各线程计数正确写入txt文件避免内容覆盖
问题背景
基于继承Thread类实现自定义线程做CPU压力测试,共启动3个线程实例,需求是每小时将每个线程统计的小时迭代次数Hcount输出到txt文件,现有代码存在两个故障:
- 最终文件中仅能看到单个线程的统计值
- 每到下一小时触发写入时,会清空txt文件原有内容,仅保留最新写入的
Hcount值,无法留存历史统计数据
原有问题代码如下:
import java.util.Random; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; public class MyThread extends Thread{ public void run() { String B;//Will hold the value of Cpointer String A;//will hold the string value of Hcount Path fileName = Path.of("D:/EEoutput/Out.txt"); Random rand = new Random(); long Hcount = 0;//counts the number of iterations in an hour long t = System.currentTimeMillis(); long end = t + 3800000*5;//a minute double a1 = 0; //random holder 1 double a2 = 0;//random holder 2 double answer = 0; // answer placeholder Long hour = System.currentTimeMillis()+3600000;//will be used to say when are we outputing the number of iterations to the file int Cpointer = 1;//will tell how many hours has passed while (System.currentTimeMillis() < end) { a1 = rand.nextDouble(); a2 = rand.nextDouble(); answer = a1 * 23 / (a2 + a1) + a2; Hcount++; if (System.currentTimeMillis() >= hour)// checks if the program needs to { B = String.valueOf(Cpointer); A=String.valueOf(Hcount); try { Files.writeString(fileName, A); } catch (IOException e) { e.printStackTrace(); } hour = System.currentTimeMillis()+3600000;//sets stop to next hour Cpointer++;//declares that another hour has passed, will be used to tell how many iterations are there in a certain hour Hcount = 0; } } } }
故障根因
- 单线程值可见问题:3个线程并发操作同一个目标文件,未做任何写入同步控制;
Files.writeString默认采用覆盖写入模式,线程触发写入的时间存在微小差,后执行写入的线程会直接冲掉其他线程刚写入的内容,最终文件仅保留最后一个完成写入操作的线程的数值。 - 历史数据丢失问题:调用
Files.writeString时未指定追加写入参数,方法默认会先截断清空文件全部原有内容,再写入新传入的内容,之前写入的所有历史记录会被直接清除。另外原代码仅写入Hcount数值,未标记线程ID、小时序号,即便写入成功也无法区分数据归属。
修复方案
修复点如下:
- 新增全局静态锁对象,所有线程实例共用同一把锁,包裹文件写入逻辑,保证同一时刻仅一个线程能执行写操作,避免并发写入互相覆盖
- 调用
Files.writeString时传入打开选项:CREATE(文件不存在则自动创建)、APPEND(新内容追加到文件末尾,不截断原有内容)、WRITE(赋予写权限),解决历史数据被覆盖清空的问题 - 写入内容补充线程ID、统计小时序号字段,每条记录末尾加换行符分隔,方便后续查看区分不同数据
- 新增输出目录存在性判断,目录不存在时自动创建,避免首次运行因路径不存在抛出IO异常
修复后完整代码:
import java.util.Random; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; public class MyThread extends Thread{ // 全局静态锁,所有线程实例共用,保证写文件操作串行执行 private static final Object FILE_WRITE_LOCK = new Object(); private static final Path OUTPUT_DIR = Path.of("D:/EEoutput"); private static final Path OUTPUT_FILE = OUTPUT_DIR.resolve("Out.txt"); public void run() { Random rand = new Random(); long Hcount = 0; // 小时级迭代计数 long startRunTime = System.currentTimeMillis(); long endRunTime = startRunTime + 3800000L *5; // 总运行时长 double a1,a2,answer; long nextHourTrigger = System.currentTimeMillis() + 3600000; // 下一次写入统计值的时间戳 int hourSeq = 1; // 已运行的小时序号 // 预创建输出目录,避免路径不存在报错 try { if (!Files.exists(OUTPUT_DIR)) { Files.createDirectories(OUTPUT_DIR); } } catch (IOException e) { e.printStackTrace(); return; } while (System.currentTimeMillis() < endRunTime) { // CPU压力计算逻辑 a1 = rand.nextDouble(); a2 = rand.nextDouble(); answer = a1 * 23 / (a2 + a1) + a2; Hcount++; // 到达小时统计节点 if (System.currentTimeMillis() >= nextHourTrigger) { String record = String.format("线程ID:%d 第%d小时 迭代次数:%d%n", this.getId(), hourSeq, Hcount); // 加锁执行写入,避免并发冲突 synchronized (FILE_WRITE_LOCK) { try { // 追加模式写入,不覆盖原有内容 Files.writeString(OUTPUT_FILE, record, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND); } catch (IOException e) { e.printStackTrace(); } } // 重置下一小时触发时间、计数、小时序号 nextHourTrigger = System.currentTimeMillis() + 3600000; hourSeq++; Hcount = 0; } } } // 启动测试示例 public static void main(String[] args) { for (int i = 0; i < 3; i++) { new MyThread().start(); } } }
修复后运行,所有线程每小时的统计数据会按时间顺序追加写入到Out.txt文件中,不会覆盖历史内容,也不会出现线程间写入内容互相冲掉的问题。
内容的提问来源于stack exchange,提问作者Shaked Ben Eliezer
相关产品推荐
相关产品推荐

