java 高并发写入文件时切换文件_RandomAccessFile文件锁踩坑--write高并发引起漏数据...

本文介绍了在Java中使用RandomAccessFile进行高并发写入文件时遇到的数据丢失问题。在Windows系统上运行正常,但在CentOS7上出现问题。通过压测和代码调试,发现即使使用文件锁也存在数据遗漏。分析了创建目录和文件、文件指针更新等可能原因,并尝试了多种优化方案,包括调整文件锁释放顺序、使用nio管道写入等,但问题依然存在。最终,通过使用FileOutputStream和FileLock解决了问题,同时强调了在高并发场景下进行严格压测和区分不同操作系统的重要性。
摘要由CSDN通过智能技术生成

背景

多线程写入文件,要考虑线程同步问题,实现数据完整落盘磁盘备份。

操作系统:

win10:没问题

centos7:有问题

public static void writeFileLock(String content, String filePath) {

File file = new File(filePath);

RandomAccessFile raf = null;

FileChannel fileChannel = null;

FileLock fileLock = null;

try {

raf = new RandomAccessFile(file, "rw");

fileChannel = raf.getChannel();

while (true) {

try {

fileLock = fileChannel.tryLock();

if (fileLock != null) {

break;

}

} catch (Exception e) {

Thread.sleep(0);

}

}

raf.seek(raf.length());

raf.write(content.getBytes());

fileLock.release();

fileChannel.close();

raf.close();

} catch (Exception e) {

log.error("写文件异常", e);

log.error("写入文件路径:{}, 文件内容:{}", filePath, content);

}

}

RandomAccessFile建立文件连接符,raf获取文件管道,文件管道获取文件锁,tryLock方法有两个特点:第一、非阻塞,调用后立刻返回;第二、没拿到锁可能返回null,也可以能抛出异常,所以if判断循环获取,异常块捕获异常再重新尝试获取锁,注意Thread.sleep(0)的作用并不是睡0秒,而是马上加入到可执行队列,等待cpu的时间分片。

这段代码承载线上的kafka多线程备份消息的任务,用lock协调多线程的写入同步,埋点监控发现,备份数据偶发遗漏,大概2.3亿数据,会有5条偏差,就是漏了。

下面记录压测思路及过程。

准备

压测代码:

private static final ExecutorService FILE_THREADS = Executors.newFixedThreadPool(100);

public void execute(String... strings) throws Exception {

int cnt = 100 * 100 * 100;

int idx = 1;

long begin = 1574305200000L;

while (idx <= cnt) {

Map map = new HashMap<>();

map.put("id", idx);

map.put("time", begin);

String timeDirectory = DateUtil.getBeforeOneHour("yyyyMMddHHmm", 8, begin);

String mm = DateUtil.getBeforeOneHour("mm", 0, begin).concat(".txt");

String json = JsonUtil.getJosnString(map).concat(System.getProperty("line.separator"));

FILE_THREADS.execute(new PersistThread(timeDirectory, mm , json));

if (idx % 10000 == 0) {

begin += 60000L;

}

idx++;

}

}

private class PersistThread extends Thread {

String time;

String filename;

String content;

PersistThread(String time, String filename, String content) {

this.time = time;

this.filename = filename;

this.content = content;

}

@Override

public void run() {

String fold

<think>嗯,用户问的是Java多线程写入文件如何保证数据不被覆写。这个问题需要仔细考虑多线程环境下文件操作的同步问题。首先,我需要回忆Java中处理多线程同步的机制,比如synchronized关键字、Lock接口,还有可能用到的线程安全类。 用户提到的是写入文件,所以需要确保每个线程在写入不会覆盖其他线程的数据。可能的情况是多个线程同打开同一个文件进行写入,导致数据混乱或者被覆盖。这候,需要考虑如何协调各个线程的写入顺序,或者让它们以某种方式互斥访问文件。 首先想到的是使用synchronized关键字,把写入操作放在同步块中。这样可以保证同一间只有一个线程执行写入,避免并发问题。但这样可能会影响性能,因为所有线程要排队等待锁。 另外,Java中的Lock接口,比如ReentrantLock,可能也是一个选择。它提供了更灵活的锁机制,比如尝试获取锁、可中断的锁等,可能在某些场景下比synchronized更高效。 还有,使用FileChannel的锁机制。FileChannel提供了lock()方法,可以锁定文件区域,这样其他线程或进程就无法访问被锁定的部分。不过需要注意,文件锁的行为可能依赖于操作系统,有些系统可能不支持共享锁,或者锁的实现方式不同。 还有一种方法是让每个线程写入不同的文件,最后合并。这样完全避免了竞争,但合并文件可能增加复杂度,特别是需要保持写入顺序的话。 或者使用单线程的ExecutorService,把所有写入任务提交到一个单独的线程中处理。这样自然就保证了顺序,但可能会成为性能瓶颈,尤其是高并发场景。 然后要考虑用户的具体需求。比如,是否需要保持写入的顺序?如果允许数据交错但不想被覆盖,可能需要追加模式。这候用FileWriter设置append参数为true,每次写入都添加到文件末尾。不过多个线程同追加的话,虽然不会覆盖,但内容可能会交错,比如一行没写完另一个线程就插入了数据。这候可能需要更细粒度的控制,比如在每次写入确保是原子操作。 比如使用BufferedWriter,但每个写操作都加锁,并且在写入后立即flush,确保数据写入,减少缓冲带来的问题。或者使用PrintWriter的println方法,因为该方法内部是否是原子操作呢?其实并不是,所以还是需要同步。 所以总结可能的方案: 1. synchronized同步代码块,控制对文件的访问。 2. 使用ReentrantLock显式锁。 3. 使用FileChannel的文件锁。 4. 单线程处理写入任务。 5. 使用追加模式并同步写操作。 还需要考虑不同情况下的性能影响。比如,如果写入非常频繁,同步块可能成为瓶颈。这候可能用队列加单线程处理会更高效,比如生产者-消费者模式,把写入任务放到队列里,由单个线程取出执行。 另外,用户是否希望每个线程写入的内容保持完整,不被其他线程的数据打断?这候需要确保每个写操作是原子的。例如,每次写入一个完整的行,并且每次写入都同步,这样即使多个线程交替写入,但每个写入操作本身是完整的。 可能还需要注意字符编码和缓冲的问题,比如使用缓冲流的话,是否及刷新缓冲区,避免数据滞留在内存中。 总之,需要根据具体场景选择合适的方法。如果用户需要简单实现,synchronized可能最直接。如果对性能有更高要求,可能需要用队列加单线程处理,或者更高级的锁机制。同要确保异常处理,比如在锁获取过程中出现异常,需要正确释放资源,避免死锁或资源泄。</think>为了在Java多线程写入文件避免数据覆写,需通过同步机制控制线程访问顺序。以下是分步解决方案及代码示例: --- ### **一、问题核心矛盾** 多线程同操作文件句柄,若未做同步控制会导致: 1. 数据交错覆盖(线程A未写完,线程B抢到资源) 2. 文件指针混乱(随机访问模式尤为明显) --- ### **二、解决方案分类** #### **方案1:同步代码块** ```java public class SyncFileWriter { private static final Object lock = new Object(); public static void write(String content) { synchronized (lock) { try (FileWriter fw = new FileWriter("file.txt", true); // true表示追加模式 BufferedWriter bw = new BufferedWriter(fw)) { bw.write(content); bw.newLine(); } catch (IOException e) { e.printStackTrace(); } } } } ``` **关键点**: - `synchronized`保证同一刻仅一个线程执行写入 - `FileWriter`开启追加模式避免覆盖 #### **方案2:显式锁(ReentrantLock)** ```java public class LockFileWriter { private static final ReentrantLock fileLock = new ReentrantLock(); public static void write(String content) { fileLock.lock(); try (FileWriter fw = new FileWriter("file.txt", true); BufferedWriter bw = new BufferedWriter(fw)) { bw.write(content); bw.newLine(); } catch (IOException e) { e.printStackTrace(); } finally { fileLock.unlock(); // 必须释放锁 } } } ``` **优势**:相比`synchronized`,可设置超、中断等精细控制 #### **方案3:单线程消费队列** ```java ExecutorService executor = Executors.newSingleThreadExecutor(); BlockingQueue<String> queue = new LinkedBlockingQueue<>(); // 生产者线程 new Thread(() -> { queue.put("data from thread"); }).start(); // 消费者线程 executor.submit(() -> { try (BufferedWriter bw = Files.newBufferedWriter(Paths.get("file.txt"))) { while (!Thread.currentThread().isInterrupted()) { String data = queue.take(); bw.write(data); bw.newLine(); bw.flush(); // 强制刷盘 } } }); ``` **特点**: - 完全避免锁竞争 - 通过队列实现异步解耦 #### **方案4:文件通道锁(NIO)** ```java try (RandomAccessFile raf = new RandomAccessFile("file.txt", "rw"); FileChannel channel = raf.getChannel()) { FileLock lock = channel.lock(); // 阻塞获取排他锁 // 写入操作... lock.release(); } ``` **注意**:文件锁在部分操作系统上为建议锁(advisory),需配合其他进程的锁机制使用 --- ### **三、性能优化技巧** 1. **缓冲机制**:使用`BufferedWriter`减少IO次数 2. **批量写入**:积累到一定数据量再批量写入 3. **异步日志框架**:直接使用`Log4j2`/`Logback`的异步Appender --- ### **四、不同场景选择建议** | 场景 | 推荐方案 | |---------|---------| | 低并发简单需求 | 方案1(synchronized) | | 需要锁超控制 | 方案2(ReentrantLock) | | 高频写入场景 | 方案3(单线程队列) | | 跨进程控制 | 方案4(文件锁) | --- ### **五、典型错误示例** ```java // 错误!多个FileWriter实例会导致覆盖 public void unsafeWrite(String content) { try (FileWriter fw = new FileWriter("file.txt")) { // 默认覆盖模式 fw.write(content); } } ``` **错误原因**:未使用追加模式且未同步,必然导致数据丢失 --- 通过合理选择同步策略+追加模式+IO优化,可有效解决多线程写文件数据完整性问题。高频场景建议优先使用队列解耦方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值