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

如何用Java模拟外部程序占用文件导致File#delete()删除失败

问题背景

当前正在排查Logback相关问题:使用日志文件读取工具glogg打开日志文件时,Compressor类无法删除该文件;该问题仅在使用glogg时出现,用记事本打开文件则无异常,推测和文件锁定机制有关。
目前需要用Java演示程序复现该场景,无需手动启动glogg。

需求说明

需要实现以下效果:

  • 启动线程A和线程B
  • 线程A负责向文件X写入内容
  • 当线程A调用File#delete()方法删除X时返回false(即删除失败)
  • 删除失败的原因是线程B占用了该文件,阻止了删除操作成功执行
已尝试的实现代码
public void run() {
    while (true) {
        logDumper.debug(bigString);
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] s) {

    final Runnable runnable = () -> {
        try {
            var fileInputStream = new FileInputStream("./test.log");
            var file = new File("./test.log");
            fileInputStream.readAllBytes();
            System.out.println("Size: " + file.length());
            final var fc = fileInputStream.getChannel();

            // this entire locking thing does not seem to have any effect
            final var shared = true; // required to avoid throwing when opening a file as read-only
            try (FileLock lock = fc.tryLock(0,file.length(), shared)) {
            } catch (NonReadableChannelException| NonWritableChannelException e) {
                logger.error("Something happened", e);
                fc.close();
            }
        } catch (IOException e) {
            logger.error("Crashed on locking", e);
            e.printStackTrace();
        }
    };

    new Thread(runnable).start();
    new Main().run();
}
原代码失效原因

你遇到的glogg可复现、记事本无法复现的核心逻辑是:glogg打开日志文件后会一直持有文件句柄用于后续增量日志监听,而记事本是把文件内容全部读入内存后就立刻关闭了文件句柄,所以不会影响删除操作。
你写的代码没有复现成功,主要有两个问题:

  1. 你用了try-with-resources语法管理FileLock,代码块执行完成后会自动释放锁、关闭文件通道和文件流,相当于线程B读完文件就立刻释放了所有占用,自然不会影响后续删除
  2. 不同系统的文件删除逻辑有差异:Windows下只要有线程持有文件的打开句柄,就会禁止删除;类Unix下文件删除默认是解除链接,只要有进程持有文件描述符,文件数据不会被释放,但delete()方法会返回成功,默认场景很难复现删除失败的效果
可运行的实现方案

Windows环境

Windows下不需要额外加锁,只要线程B一直持有文件输入流不关闭,就能阻止文件删除:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;

public class FileOccupyDemo {
    private static final Logger logger = LoggerFactory.getLogger(FileOccupyDemo.class);
    private static final String TEST_FILE = "./test.log";
    // 控制线程B是否释放文件流的开关
    private static volatile boolean keepHold = true;

    public static void main(String[] args) throws InterruptedException {
        // 初始化测试文件,模拟线程A写入的操作
        initTestFile();

        // 启动线程B,模拟glogg的行为,一直持有文件句柄
        Thread threadB = new Thread(() -> {
            try (FileInputStream fis = new FileInputStream(TEST_FILE)) {
                System.out.println("线程B已打开文件,保持持有中");
                // 循环等待,不关闭流
                while (keepHold) {
                    Thread.sleep(100);
                }
            } catch (IOException | InterruptedException e) {
                logger.error("线程B执行异常", e);
            }
        });
        threadB.start();

        // 等待1秒保证线程B已经完成文件打开操作
        Thread.sleep(1000);

        // 线程A尝试删除文件
        Thread threadA = new Thread(() -> {
            File file = new File(TEST_FILE);
            boolean deleteResult = file.delete();
            System.out.printf("线程A删除文件结果:%b%n", deleteResult);
            if (!deleteResult) {
                System.out.println("删除失败,原因:文件被线程B持有打开的句柄");
            }
            // 通知线程B可以释放资源
            keepHold = false;
        });
        threadA.start();
    }

    private static void initTestFile() {
        try (FileWriter fw = new FileWriter(TEST_FILE)) {
            fw.write("测试日志内容,模拟Logback写入的日志文件");
        } catch (IOException e) {
            logger.error("初始化测试文件失败", e);
        }
    }
}

类Unix环境(Linux/macOS)

类Unix系统默认无法通过持有句柄让File#delete()返回false,需要额外开启强制锁支持:

  1. 给对应分区挂载时添加mand参数开启强制锁支持
  2. 修改测试文件权限,关闭组执行权限:chmod g-x test.log
  3. 线程B获取排他锁且一直持有不释放,此时线程A调用delete就会返回false

内容的提问来源于stack exchange,提问作者oligofren

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 10:06:02