何时使用thread.interrupt(),何时抛出InterruptedException?

4

我有一些使用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();
        }
    }
}

重点是,对于调用线程进行测试,最好是设置中断状态呢?还是向调用方抛出新的InterruptedExeption?在上述方法中,是否有最佳实践或特定情况适用的做法?
我会在这里写下我的理解,以便您纠正我的任何误解。根据我的理解,我认为thread.interrupt()旨在不要求客户端代码处理中断,客户端代码有责任决定是否进行测试,而throws InterruptedException 是必须的,因为它是已检查的异常?
4个回答

7
请参考这个答案,它链接到了一个非常好的关于中断的讨论。主要思想是除非不可能,否则应该抛出InterruptedException。如果不可能,您应该通过调用Thread.currentThread().interrupt()来重置中断状态。
为什么重新抛出中断异常可能是不可能的呢?您可能正在调用Runnable实现类的run()方法中的Thread.sleep()(尽管看起来您并没有)。由于InterruptedException是已检查异常,并且在Runnable接口中未声明任何异常,因此您无法重新抛出异常。在这种情况下,最好重置中断状态,许多使用Runnable的容器都会正确处理。
无论如何,通过更改代码以避免吞噬异常,您正在做正确的事情。

谢谢您对使用InterruptedException和Thread.currentThread().interrupt()的情况进行清晰的解释。我现在认为我理解了基本原则:通常情况下,不要吞噬InterruptedException,正确的方法是如果可能的话重新抛出异常。如果不可能,则设置中断状态,以便关心它的任何人都可以采取行动。进一步的问题是:由于InterruptedException是一个已检查的异常,它可能会干扰调用者的逻辑顺序。所以我想知道,如果我想简化调用者的顺序,是否可以使用t.interrupt()? - Vic Lau
你说得对,这是另一个需要考虑的方面。抛出异常将强制调用线程处理异常。理论上,这就是中断器“期望”的行为,即您将立即处理中断并关闭。但是,由于现有代码的存在,您可以选择以另一种方式处理它,并显式检查每个线程是否已被中断。 - Denise
欢迎来到本站!如果你看到喜欢的答案,可以点赞或选择接受答案。;-) - Denise
好的,我刚刚接受了你的答案。很抱歉我不能为你的回答点赞,因为我的声望只有6分。:( - Vic Lau
很有趣的问题! - Denise

1

中断线程是一种机制,可以告诉该线程尽早完成执行,如果可能的话放弃当前任务。通常这被称为终止线程,您的代码应该正确地遵守这一点。可以通过使用 全局 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,因此自己抛出一个InterruptedException通常是一个边缘案例。
如果你有像现有API调用的包装器之类的东西,并且需要拦截中断进行一些清理,只需在完成后抛出相同的中断即可。抛出中断不会伤害任何人,毕竟这只是终止的信号。

0
你应该回到基础 - 你的中断是为了什么?它发生在什么时候?期望的结果是什么?在正常情况下,一个线程不会被其他人打断。
你想取消正在进行的操作吗?那就抛出或返回线程。你想消耗中断还是保留给被调用的模块?
在大多数情况下,你可能想要取消操作并退出。

runRemoteScript将在FutureTask期间被多次调用。每次调用都会向远程机器上的脚本发送一些数据,当脚本正在运行时,它具有文件锁,如果我只是中断或返回正在进行的操作,则文件锁仍然存在于机器上,因为脚本仍在运行。以这种方式,由于已存在的文件锁,同一脚本的后续运行将没有影响,因此我需要在布尔字段中记录中断,并在运行脚本的这一轮之后设置中断状态。 - Vic Lau
在运行相同脚本数据的下一轮之前,我可以检查是否发生了中断。如果是,我可以取消将剩余数据发送到远程脚本。实际上,我只会中止可能需要很长时间才能完成的那些脚本,并让后续脚本运行。 - Vic Lau
如果你想永远等待它完成,那就永远等待吧。为什么要设置超时?因为你可能想要取消它,或者捕获错误。你真的应该优雅地取消一个脚本,或记录错误并阻止重试,除非你的文件被解锁。 - Dennis C
是的,你说得对。在这里取消脚本的过程并不十分优雅。但我的情况是远程脚本并非由我们团队维护,有时它们甚至无法正常工作。实际上,这不是超时问题,而是脚本无法正常工作的问题。这只是我们手动停止那些非常缓慢或损坏的脚本的一种方法。 - Vic Lau

0
何时使用 thread.interrupt()
每当您需要中断线程时,这种情况不应该经常发生。
何时抛出 InterruptedException
只有在需要捕获并重新抛出它时才需要,这种情况几乎不会发生。
请注意,您选择了一个非常糟糕的示例。如果需要,您应该只调用 Process.waitFor() 并在返回时获取退出值。

我认为你的话并没有帮助我理解一个通用原则,让我仍然困惑于何时使用哪种方法。根据你的回答,任何一种方法都可以帮助指示是否发生了中断。但是真的非常感谢你建议我使用Process.waitFor()来查找返回代码并等待完成。谢谢! - Vic Lau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接