【问题标题】:When to use thread.interrupt() and when to throws InterruptedException何时使用 thread.interrupt() 以及何时抛出 InterruptedException
【发布时间】:2013-11-13 04:18:10
【问题描述】:

我有一些使用Java中断机制来完成我的工作的经验,但是我目前还不清楚什么时候应该设置当前线程的中断状态,什么时候应该抛出InterruptedException?

而且,为了让您更清楚,这是我之前编写的示例。

这是我开始工作之前的代码:

/*
 * Run a command which locates on a certain remote machine, with the
 * specified timeout in milliseconds.
 * 
 * This method will be invoked by means of
 *     java.util.concurrent.FutureTask
 * which will further submmited to a dedicated
 *     java.util.concurrent.ExecutorService
 */
public void runRemoteSript(String command, long timeout) {
    Process cmd = null;
    try {
        cmd = Runtime.getRuntime().exec(command);

        boolean returnCodeOk = false;
        long endTime = System.currentTimeMillis() + timeout;

        // wait for the command to complete
        while (!returnCodeOk && System.currentTimeMillis() < endTime) {

            // do something with the stdout stream
            // do something with the err stream

            try {
                cmd.exitValue();
                returnCodeOk = true;
            } catch (IllegalThreadStateException e) { // still running
                try {
                    Thread.sleep(200);
                } catch (InterruptedException ie) {
                    // The original code just swallow this exception
                }
            }
        }

    } finall {
        if (null != cmd) {
            cmd.destroy();
        }
    }
}

我的目的是中断命令,因为一些远程脚本在完成之前会消耗大量时间。因此 runRemoteScript 可以完成或手动停止。这是更新的代码:

public void cancel(String cmd) {
    // I record the task that I've previously submitted to
    // the ExecutorService.
    FutureTask task = getTaskByCmd(cmd);

    // This would interrupt the:
    //     Thread.sleep(200);
    // statement in the runRemoteScript method.
    task.cancel(true);
}

public void runRemoteSript(String command, long timeout) {
    Process cmd = null;
    try {
        cmd = Runtime.getRuntime().exec(command);

        boolean returnCodeOk = false;
        long endTime = System.currentTimeMillis() + timeout;

        // wait for the command to complete
        **boolean hasInterruption = false;**
        while (!returnCodeOk && System.currentTimeMillis() < endTime) {

            // do something with the stdout stream
            // do something with the err stream

            try {
                cmd.exitValue();
                returnCodeOk = true;
            } catch (IllegalThreadStateException e) { // still running
                try {
                    Thread.sleep(200);
                } catch (InterruptedException ie) {
                    // The updated code comes here:
                    hasInterruption = true; // The reason why I didn't break the while-loop
                                            // directly is: there would be a file lock on
                                            // the remote machine when it is running, which
                                            // will further keep subsequent running the same
                                            // script. Thus the script will still running
                                            // there.
                }
            }
        }

        // let the running thread of this method have the opportunity to
        // test the interrupt status
        if (hasInterruption) {
            Thread.currentThread().interrupt();
        }

        // Will it be better if I throws a InterruptedException here?

    } finall {
        if (null != cmd) {
            cmd.destroy();
        }
    }
}

重点是,设置中断状态来调用线程进行测试会更好吗?或者只是向调用者抛出一个新的 InterrutedExeption是否有任何最佳做法或特定情况更适合上述方法之一?

我会在这里写下我的理解,如果我误解了其中任何一个,您可以纠正我的理解。根据我的理解,我认为 thread.interrupt() 旨在不要求客户端代码处理中断,并且由客户端代码负责决定是否对其进行测试,而 throws InterruptedException 是必须的,因为它是检查异常?

【问题讨论】:

    标签: java multithreading interrupt-handling


    【解决方案1】:

    请参阅this other answer,它链接到关于中断的非常好的讨论。主要思想是你应该抛出InterruptedException,除非是不可能的。如果不可能,你应该调用Thread.currentThread().interrupt()重置中断状态。

    为什么不可能重新抛出被中断的异常?您可能在实现 Runnable 的类的 run() 方法内调用 Thread.sleep()(看起来不像您)。因为检查了InterruptedException,并且在Runnable接口中没有声明run方法抛出任何异常,所以不能重新抛出异常。在这种情况下,最好重置中断状态,许多使用 Runnable 的容器都会正确处理。

    在任何情况下,您都可以通过更改代码来做正确的事情,这样它就不会吞下异常。

    【讨论】:

    • :) 感谢您对使用 InterruptedException 和 Thread.currentThread().interrupt() 的情况的清晰解释。我想我现在明白了基本原理:一般情况下,不要吞下InterruptedException,正确的方法是如果可能,重新通过异常。如果不可能,请设置中断状态,以便任何关心它的人都可以对其采取措施。另一个问题:由于 InterruptedException 是一个检查异常,它可能会打扰调用者的逻辑序列。所以我想知道如果我想简化调用者的序列,我可以使用 t.interrupt() 吗?
    • 你说得对,这是另一个需要考虑的方面。抛出异常将强制调用线程处理异常。从理论上讲,这就是中断器的“预期”,即您将立即处理中断并关闭。但是,由于现有代码,您可以选择以其他方式处理它并显式检查每个线程以查看它是否被中断。
    • ps。欢迎来到本站!当你看到你喜欢的答案时,你应该投票/接受;-)
    • 好的,我刚刚接受了你的回答。很抱歉,我只有 6 分的声望,所以无法支持​​您的回答。 :(
    • np,非常有趣的问题!
    【解决方案2】:

    中断线程是一种告诉该线程尽早完成执行的机制,如果可能的话放弃当前的任务。通常调用它来终止线程,并且您的代码应该正确地尊重它。这可以通过一个 global try/catch 块来完成,这样线程会立即跳出任何 while 循环(如果有的话)。

    如果您使用阻塞代码,您可以假设内部代码最终会在适当的时候抛出 InterruptedException。如果你的计算时间很长,你应该经常检查是否有中断。

    阻塞代码的正确实现应该如下所示:

    public void run() {
      try {
        while (this.running) {
          doSomethingThatBlocks();
        }
      } catch (InterruptedException e) {
        // maybe log if this wasn't expected
      } finally {
        cleanup();
      }
    }
    

    对于非阻塞代码也是如此,你必须自己检查中断,应该是这样的:

    public void run() {
      while (this.isInterrupted() == false) {
        calculation.nextStep();
      }
    }
    

    如果您正在设计一个阻塞 API 调用,您只需要实际抛出一个 InterruptedException。但由于几乎所有可能阻塞的 Java 函数都已经在必要时抛出了 InterruptedException,因此不得不自己抛出一个通常是一种极端情况。

    如果您确实对现有 API 调用进行了封装,并且需要拦截中断以进行一些清理,则只需在完成后抛出相同的中断即可。打断并没有伤害到任何人,毕竟它只是一个终止的信号。

    【讨论】:

      【解决方案3】:

      你应该回到基本的 - 你的中断是什么?什么时候发生?什么是预期结果?正常情况下,线程永远不会被其他人打断。

      您想取消正在进行的操作吗?然后抛出它或返回线程。 您想消耗中断的还是保留它以供被调用者模块使用?

      在大多数情况下,您可以取消操作并退出。

      【讨论】:

      • runRemoteScript 将在 FutureTask 期间多次调用。每次调用都会向远程机器上的脚本发送一条数据,脚本在运行时有一个文件锁,如果我只是中断或从正在进行的操作返回,文件锁仍然在机器上,因为脚本仍在运行。这样以后同一个脚本的运行由于文件锁的存在是没有作用的,所以我需要将中断记录在一个布尔字段中,并设置本轮脚本运行后的中断状态。
      • (...continued) 在下一轮使用不同的脚本数据运行相同的脚本之前,我可以检查是否发生了中断。如果是,我可以取消将其余数据发送到远程脚本。其实我只会打断那些可能需要很长时间才能完成的脚本,让后面的脚本运行。
      • 如果你想永远等待它完成,那就永远等下去。为什么要超时?因为您可能想取消它,或者捕获错误。你真的应该优雅地取消脚本,或者记录错误并阻止重试,除非你的文件被解锁
      • 是的,你是对的。这里取消脚本的过程不是很优雅。但是我的情况是我们团队没有维护远程脚本,有时甚至无法正常工作。实际上,这不是超时问题,而是无法正常工作的脚本问题。这只是我们手动停止那些非常慢的脚本或损坏的脚本的一种方法。
      【解决方案4】:

      什么时候使用thread.interrupt()

      当你需要中断一个线程时,不应该很频繁。

      以及何时抛出 InterruptedException

      仅当您需要捕捉并重新抛出它时,几乎永远不会。

      请注意,您选择了一个非常糟糕的示例。您只需调用Process.waitFor(),并在返回时获取退出值(如果需要)。

      【讨论】:

      • 我认为你的话对一般原则没有帮助,我仍然对决定何时使用任何一种方法感到困惑。根据您的回答,任何一种方法都可以帮助表明存在中断。但非常感谢您建议我使用 Process.waitFor() 来查找返回码并等待完成。谢谢!
      猜你喜欢
      • 2018-07-26
      • 2012-05-24
      • 2013-04-10
      • 1970-01-01
      • 1970-01-01
      • 2010-11-08
      • 2010-12-25
      • 2020-09-27
      • 1970-01-01
      相关资源
      最近更新 更多