简体   繁体   English

理解 Java 中的线程中断

[英]Understanding thread interruption in Java

I was reading thread interrupting from this article.我正在阅读这篇文章中的线程中断。 It says following:它说如下:

Before a blocking code throws an InterruptedException , it marks the interruption status as false.在阻塞代码抛出InterruptedException ,它将中断状态标记为 false。 Thus, when handling of the InterruptedException is done, you should also preserve the interruption status by callingThread.currentThread().interrupt() .因此,当处理完InterruptedException ,您还应该通过callingThread.currentThread().interrupt()保留中断状态。

Let's see how this information applies to the example below.让我们看看这些信息如何应用于下面的示例。 In the task that is submitted to the ExecutorService , the printNumbers() method is called twice.在提交给ExecutorService的任务中, printNumbers()方法被调用两次。 When the task is interrupted by a call toshutdownNow() , the first call to the method finishes early and then the execution reaches the second call.当任务被调用toshutdownNow()中断时,对该方法的第一次调用提前结束,然后执行到达第二次调用。 The interruption is called by the main thread only once.中断只被主线程调用一次。 The interruption is communicated to the second execution of the printNumber() method by the call to Thread.currentThread().interrupt() during the first execution.在第一次执行期间,通过调用Thread.currentThread().interrupt()将中断传达给printNumber()方法的第二次执行。 Hence the second execution also finishes early just after printing the first number.因此,第二次执行也会在打印第一个数字后提前结束。 Not preserving the interruption status would have caused the second execution of the method to run fully for 9 seconds.不保留中断状态会导致方法的第二次执行完全运行 9 秒。

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

I tried running above code and it prints:我尝试运行上面的代码并打印:

01230

When I comment Thread.currentThread().interrupt();当我评论Thread.currentThread().interrupt(); from catch block, it prints:catch块,它打印:

01230123456789

Though I feel I understand explanation before code, I dont think I understand why the explanation is correct, partly because I did not find any related explanation / sentence in the official doc.虽然我觉得我在代码之前理解了解释,但我认为我不明白为什么解释是正确的,部分原因是我在官方文档中没有找到任何相关的解释/句子。 Here are my specific doubts:以下是我的具体疑问:

  1. Does that mean we need every method call in Runnable submitted to ExecutorService.submit() to have catch InterruptedException and call Thread.currentThread().interrupt() in it for whole Runnable body to interrpt?这是否意味着我们需要将Runnable每个方法调用提交给ExecutorService.submit()来捕获InterruptedException并在其中调用Thread.currentThread().interrupt()以便整个Runnable主体进行中断? If we miss to catch InterruptedException and call Thread.currentThread().interrupt() in any method call in Runnable , then it will not be interrupted?如果我们在Runnable任何方法调用中错过捕获InterruptedException并调用Thread.currentThread().interrupt() ,那么它不会被中断?

  2. If above is correct, why there is not such explanation in the doc of Thread.interrupt() ?如果以上是正确的,为什么Thread.interrupt()文档中没有这样的解释?

  3. Is it like if we want to do any closure task on interruption in any method, then only we should catch InterruptedException , perform any task to be done and then call Thread.currentThread().interrupt() ?是不是如果我们想在任何方法的中断上做任何关闭任务,那么我们应该只捕获InterruptedException ,执行任何要完成的任务然后调用Thread.currentThread().interrupt()

  4. If answer to question 3 is yes, then when we should be doing closure task?如果问题 3 的答案是肯定的,那么我们应该什么时候做关闭任务? I feel any closure task should be done before Thread.currentThread().interrupt() , but in the given example, break is called after Thread.currentThread().interrupt() .我觉得任何关闭任务都应该在Thread.currentThread().interrupt()之前完成,但在给定的例子中, breakThread.currentThread().interrupt()之后被调用。

  5. After thinking on question 4 a bit more, I feel I dont understand it clearly how thread interruption is handled.对问题4多想了几句,感觉线程中断是怎么处理的,没看清楚。 Earlier I felt interrupted thread simply gets killed immediately, but that does not seem to be the case.早些时候我觉得被中断的线程会立即被杀死,但情况似乎并非如此。 How thread interruption occurs?线程中断是如何发生的? Is there any oracle official link explaining the same?是否有任何 oracle 官方链接解释相同?

Thread interruption is not something that kills a thread right away.线程中断不是立即杀死线程的事情。 It is a suggestion for the thread to end themself.这是线程结束自己的建议。

  1. The code in your thread will not terminate automatically.线程中的代码不会自动终止。 So your code should react to thread interuption by terminating gracefully.所以你的代码应该通过优雅地终止来响应线程中断。 One good practice is to reset the interrupted flag everytime it was consumed, by calling Thread.currentThread().interrupt() .一种好的做法是在每次使用时重置中断标志,方法是调用Thread.currentThread().interrupt() Following operations that respect the status will throw InterruptedException, and will not just continue.遵循状态的操作将抛出 InterruptedException,并且不会继续。

  2. Thread.currentThread().interrupt(); should be the first statement in the catch block, to make sure any cleanup doesnt block again.应该是 catch 块中的第一条语句,以确保任何清理不会再次阻塞。 (Using finally for cleanup is also a good idea) (使用 finally 进行清理也是一个好主意)

  3. In that specific case I would have pulled the try / catch out of the for loop.在那种特定情况下,我会将 try / catch 拉出 for 循环。

  4. It is basically a suggestion for the thread to end itself.它基本上是线程结束自身的建议。

Java language specification 17.2.3 Java 语言规范 17.2.3

Interruption actions occur upon invocation of Thread.interrupt, as well as methods defined to invoke it in turn, such as ThreadGroup.interrupt.中断操作发生在调用 Thread.interrupt 时,以及定义为依次调用它的方法,例如 ThreadGroup.interrupt。

Let t be the thread invoking u.interrupt, for some thread u, where t and u may be the same.设 t 为调用 u.interrupt 的线程,对于某些线程 u,其中 t 和 u 可能相同。 This action causes u's interruption status to be set to true.此操作会导致您的中断状态设置为 true。

Additionally, if there exists some object m whose wait set contains u, then u is removed from m's wait set.此外,如果存在某个对象 m 的等待集包含 u,则将 u 从 m 的等待集中删除。 This enables u to resume in a wait action, in which case this wait will, after re-locking m's monitor, throw InterruptedException.这使您能够在等待操作中恢复,在这种情况下,此等待将在重新锁定 m 的监视器后抛出 InterruptedException。

Invocations of Thread.isInterrupted can determine a thread's interruption status.调用 Thread.isInterrupted 可以确定线程的中断状态。 The static method Thread.interrupted may be invoked by a thread to observe and clear its own interruption status.线程可以调用静态方法 Thread.interrupted 来观察和清除自己的中断状态。

- ——

Oracle Tutorial about interrupts Oracle 中断教程

The Interrupt Status Flag The interrupt mechanism is implemented using an internal flag known as the interrupt status.中断状态标志 中断机制是使用称为中断状态的内部标志实现的。 Invoking Thread.interrupt sets this flag.调用 Thread.interrupt 设置此标志。 When a thread checks for an interrupt by invoking the static method Thread.interrupted, interrupt status is cleared.当线程通过调用静态方法 Thread.interrupted 检查中断时,中断状态被清除。 The non-static isInterrupted method, which is used by one thread to query the interrupt status of another, does not change the interrupt status flag.一个线程使用非静态 isInterrupted 方法来查询另一个线程的中断状态,它不会更改中断状态标志。

By convention, any method that exits by throwing an InterruptedException clears interrupt status when it does so.按照惯例,任何通过抛出 InterruptedException 退出的方法都会在它这样做时清除中断状态。 However, it's always possible that interrupt status will immediately be set again, by another thread invoking interrupt.然而,中断状态总是有可能被另一个调用中断的线程立即再次设置。

The biggest misconception with thread interruption is that this works out of the box.对线程中断的最大误解是它是开箱即用的。 Yes, the basic mechanics to set the interrupt flag of a thread are made available to deal with thread interruption, but how a thread should do this is still up to the developer.是的,设置线程中断标志的基本机制可以用来处理线程中断,但是线程应该如何做到这一点仍然取决于开发人员。 See the note on ExecutorService shutdownNow method:参见 ExecutorService shutdownNow方法的注释:

There are no guarantees beyond best-effort attempts to stop processing actively executing tasks.除了尽力尝试停止处理正在执行的任务之外,没有任何保证。 For example, typical implementations will cancel via Thread.interrupt, so any task that fails to respond to interrupts may never terminate.例如,典型的实现将通过 Thread.interrupt 取消,因此任何未能响应中断的任务可能永远不会终止。

For example:例如:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
      printNumbers(); // first call
      printNumbers(); // second call
    });
    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);
    }
  }
}

Although the executor was shutdown and the thread was interrupted, it prints:虽然执行程序被关闭并且线程被中断,但它打印:

01234567890123456789

This is because there is no thread interruption handling whatsoever.这是因为没有任何线程中断处理。

If you would for example test if the thread was interrupted using Thread.interrupted() , then you can actually deal with an interruption within your thread.例如,如果您要使用Thread.interrupted()测试线程是否被中断,那么您实际上可以处理线程内的中断。

To halt after the first printNumbers you can do:要在第一个printNumbers之后停止,您可以执行以下操作:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
      printNumbers(); // first call

      // this checks the interrupt flag of the thread, which was set to
      // true by calling 'executor.shutdownNow()'
      if (Thread.interrupted())
        throw new RuntimeException("My thread got interrupted");

      printNumbers(); // second call
    });
    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);
    }
  }
}

This wil execute the first printNumbers and print:这将执行第一个printNumbers并打印:

0123456789

The problem with your example is that whenever you catch InterruptedException , the interrupt flag of the thread is reset to false .您的示例的问题在于,无论何时捕获InterruptedException ,线程的中断标志都会重置为false

Therefore, if you would execute:因此,如果您要执行:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    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) {
        // the interrupt flag is reset, but do not do anything else
        // except break from this loop
        break;
      }
    }
  }
}

This wil interrupt the first call to printNumbers , but due to the catch of InterruptedException , the thread is not considered interrupted anymore and therefore the second execution of printNumbers will continue and the sleep is never interrupted anymore.这将中断对printNumbers的第一次调用,但由于捕获了InterruptedException ,线程不再被视为中断,因此printNumbers的第二次执行将继续并且sleep不再被中断。 The output will be:输出将是:

0120123456789

However, when you manually set back the interrupt flag after the catch, during the second execution of printNumbers the sleep is interrupted due to the interrupt flag being true and you immediately break out of the loop:但是,当您在捕获后手动设置中断标志时,在第二次执行printNumbers期间,由于中断标志为真, sleep被中断,您立即跳出循环:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    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) {
        // the interrupt flag is reset to false, so manually set it back to true
        // and then break from this loop
        Thread.currentThread().interrupt();
        break;
      }
    }
  }
}

It prints 012 from the first printNumbers execution and 0 from the second printNumbers execution:它从第一次printNumbers执行中打印012 ,从第二次printNumbers执行中打印0

0120

But remember, this is only because you are manually implementing thread interruption handling, in this case due to using Thread.sleep .但请记住,这只是因为您正在手动实现线程中断处理,在这种情况下是由于使用Thread.sleep As shown by this last example:如最后一个示例所示:

public class Hello {
  public static void main(String[] args) throws InterruptedException {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
      printNumbers(); // first call
      printNumbersNotInterruptible(); // 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();
        break;
      }
    }
  }

  private static void printNumbersNotInterruptible() {
    for (int i = 0; i < 10; i++) {
      System.out.print(i);
    }
  }
}

This will print out:这将打印出:

0120123456789

This is because printNumbersNotInterruptible does not deal with thread interruption and therefore completes its full execution.这是因为printNumbersNotInterruptible不处理线程中断并因此完成其完整执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM