在shutdown hook中中断Java中的所有线程

时间:2012-07-19 18:59:58

标签: java multithreading shutdown-hook

我有一个简单的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个黑客:

Hack#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();
            }
        }
    }
}));

Hack#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();
        }
}));

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

3 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:2)

泰勒说的话,但更详细一点:

  • 保持对关闭钩子可以访问它们的线程的引用。
  • 在线程上有关闭钩子调用中断。
  • 检查线程的代码以确保它们实际上响应中断(而不是吃掉InterruptedException和浮躁,这是很多代码的典型代码)。中断应该提示线程停止循环或阻塞,包装未完成的业务,然后终止。
  • 对于您不想继续操作直到完成的每个主题,请检查该主题是否处于活动状态,如果是,请调用它加入,设置超时以防它在合理的时间内没有完成,在这种情况下,您可以决定是否删除该文件。

答案 2 :(得分:2)

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

使用 finally 块删除临时文件。

依靠ShutdownHooks进行资源管理是一个非常糟糕的主意,并且使代码很难在更大的系统中进行组合或重用。将资源从线程交给线程是一个更糟糕的想法。像文件和流这样的资源是线程之间共享的最危险的东西。可能很少从中获益,每个线程使用库的 createTempFile 方法独立获取临时文件并使用 try / finally <管理它们的使用和删除更有意义/强>

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

  1. 磁盘上的位置是不透明的(与程序无关且不直接由程序使用)
  2. filename无关紧要
  3. filename保证互相排斥
  4. 如果您手动滚动代码以自行创建和命名临时文件,则上面的第三个很难实现。它可能很脆弱,在最糟糕的时候会失败(任何人都可以在凌晨3点开始寻呼?)。

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

    以下是高级流程:

    1. 使用 Files.createTempFile()获取路径(或使用 File.createTempFile(使用 File.createTempFile)使用旧版Java 7之前的代码文件
    2. 根据需要使用临时文件
    3. 删除文件
    4. 这类似于 InputStream 或其他需要手动管理的资源。

      显式资源管理的一般模式( AutoCloseable try-with-resources 不可用时)如下所示。

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

      对于路径,它看起来像这样:

      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 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);
          }
      }