4

我有一个简单的 java 程序,它创建了一系列存储在本地 tmp 目录中的临时文件。我添加了一个简单的关闭挂钩,它遍历所有文件并删除它们,然后在退出程序之前删除 tmp 目录。这是代码:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        File tmpDir = new File("tmp/");
        for (File f : tmpDir.listFiles()) {
            f.delete();
        }
        tmpDir.delete();
    }
}));

我的问题是创建这些文件的线程在启动关闭挂钩时可能没有终止,因此可能在listFiles()调用之后创建了一个文件。这会导致 tmp 目录不会被删除。我想出了 2 hacks:

黑客#1:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        File tmpDir = new File("tmp/");
        while (!tmp.delete()){
                for (File f : tmpDir.listFiles()) {
                f.delete();
            }
        }
    }
}));

黑客#2:

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
    @Override
    public void run() {
        try{
            Thread.sleep(1000);
        } catch(InterruptedException e){
            e.printStackTrace();
        }
        File tmpDir = new File("tmp/");
        for (File f : tmpDir.listFiles()) {
            f.delete();
        }
            tmpDir.delete();
        }
}));

两者都不是特别好的解决方案。理想的做法是让关闭钩子等到所有线程都终止后再继续。有谁知道这是否可以做到?

4

3 回答 3

7

只需跟踪所有正在运行的线程,然后.join()在关闭程序之前跟踪它们。

这是对问题标题的回答,因为 ewok 说他不能使用.deleteOnExit()

于 2012-07-19T19:05:27.770 回答
2

更新: Tyler Heiks准确地指出deleteOnExit()不是一个有效的解决方案,因为 OP 尝试了它并且它没有工作。我正在提供替代解决方案。这又是间接的,但主要是因为使用线程和 ShutdownHook 的原始设计存在致命缺陷。

使用finally块删除临时文件。

依赖 ShutdownHooks 进行资源管理是一个非常糟糕的主意,并且使得代码很难在更大的系统中编写或重用。将资源从一个线程传递到另一个线程是一个更糟糕的主意。文件和流等资源是线程之间共享的最危险的东西之一。从中获得的收益可能很少,每个线程使用库的createTempFile方法独立获取临时文件并使用try/finally管理它们的使用和删除会更有意义。

处理系统上的临时文件的惯例是将它们视为块框,其中:

  1. 磁盘上的位置是不透明的(与程序无关且不直接由程序使用)
  2. 文件名无关紧要
  3. 文件名保证是互斥的

如果您自己手动创建和命名临时文件,则上述第三项很难实现。在最坏的情况下(凌晨 3 点寻呼机有人吗?),它很可能会变得脆弱和失败。

您提出的算法可以删除由巧合共享同一父目录的其他进程创建的文件。对于其他程序的稳定性来说,这不太可能是一件好事。

这是高级流程:

  1. 使用Files.createTempFile()获取路径(或使用File.createTempFile()使用旧的 Java 7 之前的代码文件
  2. 根据需要使用临时文件
  3. 删除文件

这类似于InputStream或其他需要手动管理的资源。

显式资源管理的一般模式(当AutoCloseabletry-with-resources不可用时)如下。

Resource r = allocateResource();
try {
   useResource(r);
} finally {
   releaseResource(r);
}

Path的情况下,它看起来像这样:

Path tempDir = Paths.get("tmp/);
try {
    Path p = Files.createTempFile(tempDir, "example", ".tmp");
    try {
       useTempFile(f);
    } finally {
       Files.delete(f);
    }
} finally {
    Files.delete(tempDir);
}

在 Java 7 之前的旧版本中,与File的使用如下所示:

File tempDir = new File("tmp/");
try {
    File f = File.createTempFile(tempDir, "example", ".tmp");
    try {
       useTempFile(f);
    } finally {
       if (!f.delete()) {
          handleFailureToDeleteTempFile(f);
       }
    }
} finally {
    if (!tempDir.delete()) {
        handleFailureToDeleteTempDir(tempDir);
    }
}
于 2014-06-07T01:33:05.533 回答
2

泰勒说了什么,但更详细一点:

  • 保留对关闭钩子可以访问它们的线程的引用。
  • 让线程上的关闭挂钩调用中断。
  • 检查线程的代码以确保它们确实响应中断(而不是吃掉 InterruptedException 并在很多代码中犯错)。中断应该提示线程停止循环或阻塞,结束未完成的业务,并终止。
  • 对于在完成之前不想继续的每个线程,检查线程是否处于活动状态,如果是,则对其调用 join,设置超时以防它无法在合理的时间内完成,在这种情况下,您可以决定是否删除文件。
于 2012-07-20T14:01:44.063 回答