何时调用Thread.currentThread().interrupt(),何时不需要调用?

11

从互联网上的多篇文章中可以得知,不建议吞咽InterruptedException。如果我要重用同一线程,使用线程池执行器会更加合理,就像这样:

public static void main(String[] args) throws InterruptedException {

    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<?> future = executor.submit(() -> {
        printNumbers(); // first call
        printNumbers(); // second call
    });
    Thread.sleep(3_000);                                     
    executor.shutdownNow();  // will interrupt the task
    executor.awaitTermination(3, TimeUnit.SECONDS);
}

private static void printNumbers() {
    for (int i = 0; i < 10; i++) {
        System.out.print(i);
        try {
            Thread.sleep(1_000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // preserve interruption status
            break;
        }
    }
}

上面的代码示例来自DZone

但是如果每次都像这样创建新线程:

Object LOCK = new Object();

public void doSomeJob() {

    myThread = new Thread(new Runnable() {
        public void run() {
            try {
               synchronized(LOCK) {
               System.out.println("Inside run");
               LOCK.wait();
              }
            } catch(InterruptedException ignored){}
        }
    }
}

我还需要调用Thread.currentThread().interrupt();吗?那样做有意义吗?

好的参考资料:

https://codepumpkin.com/interrupt-interrupted-isinterrupted-java-multithreading/

http://michaelscharf.blogspot.com/2006/09/dont-swallow-interruptedexception-call.html


2
此外,由于中断会打破循环的效果,因此最好将try/catch InterruptedException放在循环之外。 - Andy Turner
好的发现。你是正确的! - gooogle
根据DZone的建议,我更正了关于循环的部分,我理解样例的作者意思是要打破循环。如果您有不同意见,请添加评论说明代码已经被更改。 - Alex Martian
我看到了一个wait方法,但是没有看到任何同步块。你需要在对象上获取锁才能使用wait方法释放它。我的意思是:你应该在wait调用周围放置一个synchronized(lock){...}块。 - Victor
这是另一个链接,其中解释了一些相关问题:https://www.javaspecialists.eu/archive/Issue056.html - Marco13
抱歉我挑剔了一下...它不应该是 private static final Object LOCK = new Object(); 吗?或者至少只用 final 来保护类免受错误的外部编程影响?就像我在学习时了解到的,任何线程问题都是最难找到、跟踪和测试的。 - Dirk Schumacher
3个回答

9

基于Brian Goetz的著作《Java并发编程实战》 第7.1.2节,我将给出一个答案。

在你的第一个示例中,你使用了ExecutorServiceExecutorService管理自己的线程。你不是这些Threads的所有者,因此你不知道中断对它们的意义(例如ThreadPool可能选择杀死被中断的Threads并创建新的线程)。这就是为什么当您向此池提交可取消任务时,应保留中断状态的原因。以下引文适用于此情况:

任务不在它们所拥有的线程中执行。它们借用由服务(如线程池)拥有的线程。不拥有线程的代码(对于线程池来说,线程池实现之外的任何代码)应该小心地保留中断状态,以便拥有代码最终可以处理它,即使“访客”代码也处理了中断。(如果你在代管别人家时,你不会扔掉他们离开时接收到的信件——你会保存它,等他们回来后再处理,即使你读了他们的杂志。)
在第二种情况下,您手动管理Thread的一个实例。因此,您是它的所有者。因此,您可以决定对这个Thread进行什么样的中断,并且如果您不想为其应用任何Thread中断策略,则不必在第二种情况下保留中断状态
你不应该捕获InterruptedException并在catch块中什么也不做,除非你的代码实际上正在为线程实现中断策略。
请注意,线程中断策略任务取消策略不同:
  1. 线程中断策略 - 定义了线程对中断的反应方式(例如,ThreadPool可能会终止被中断的线程并创建一个新的线程)。它由线程所有者定义。
  2. 任务取消策略 - 定义了任务对取消操作的反应方式。通常使用中断来实现取消操作。实现任务的人可以选择是否响应中断。如果您的任务调用抛出InterruptedException的方法,则很容易实现此目标。或者您可以通过调用Thread::isInterrupted(例如在循环中)来检查线程的中断标志。任务的实现者选择如何处理这个问题。

此外,您不应该对线程中断策略做任何假设(如果您不是Thread的所有者)。这就是为什么保留中断状态或重新抛出InterruptedException被认为是一种良好的实践。


3
这是一个正确的答案!将其标记为正确答案会很好。 - Victor
好的发现。在输入问题时忘记添加synchronized(LOCK)了。已更新。 - gooogle
那么,什么情况下可以决定不为线程关联任何“线程中断策略”? - gooogle
@gooogle 线程的所有者决定如果线程正在执行的任务被中断要做什么。您可以选择什么也不做,但也许所有者想要做一些事情-清理一些资源、终止线程并重新创建等等。这完全取决于线程的所有者。 - Michał Krzywański

3
如果您的锁来自java.util.concurrent.locks.Lock并且是可中断的(使用.lockInterruptibly()),那么中断进程确实有意义,因此可以中断和取消所有内容。
请阅读documentation中的Implementation Considerations章节。
但是,如果您的锁是非可中断的(使用.lock()),则没有意义,因为您将无法中断该锁。
在您的情况下,您正在使用可中断的wait(),如here所述,并将抛出InterruptedException

wait()的情况下,我是否仍然需要调用Thread.currentThread().interrupt() - gooogle
您可以中断任何线程,这将导致锁被释放。确定要中断哪个线程将取决于您的代码实现。如果您想中断整个进程,应中断主线程。 - IQbrod
有点害羞地问这个问题,但是,中断这个线程会如何中断我的主线程? - gooogle
你也可以通过 myThread.interrupt() 中断其中一个子线程。 - IQbrod
@gooogle 它会像这里所讨论的那样吞掉中断 :) - IQbrod
中断子线程不会中断父线程,因此在这种情况下,您不需要调用Thread.currentThread().interrupt()甚至不需要捕获它。 - IQbrod

2
在你的问题中,DZone链接https://dzone.com/articles/understanding-thread-interruption-in-java中的解释非常详细。 Thread.currentThread().interrupt();会引发被阻塞方法(sleep)之前已经清除的中断异常状态。 这是为了确保第二个循环也被中断(它将捕获异常,因为它在同一个线程上)。
在我结束之前,我想强调一个关于线程中断状态发生变化的重要细节,当阻塞代码通过抛出InterruptedException来响应中断时。为了避免混淆,我一直没有提及这个细节。
在阻塞代码抛出InterruptedException之前,它会将中断状态标记为false。因此,在处理InterruptedException时,您还应通过调用Thread.currentThread().interrupt()来保留中断状态。
让我们看看如何将这些信息应用于下面的示例。在提交给ExecutorService的任务中,printNumbers()方法被调用两次。当任务被调用shutdownNow()中断时,第一次调用该方法会提前完成,然后执行到第二次调用。中断仅由主线程调用一次。通过第一次执行期间对Thread.currentThread().interrupt()的调用,将中断传递给printNumber()方法的第二次执行。因此,第二次执行在打印第一个数字后也很快地完成。如果不保留中断状态,第二次执行方法将完全运行9秒钟。
在哪里使用Thread.currentThread().interrupt();取决于你的代码,第二个例子不完整,无法理解它的必要性。

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