[英]Understanding thread interruption in Java
我正在阅读这篇文章中的线程中断。 它说如下:
在阻塞代码抛出
InterruptedException
,它将中断状态标记为 false。 因此,当处理完InterruptedException
,您还应该通过callingThread.currentThread().interrupt()
保留中断状态。让我们看看这些信息如何应用于下面的示例。 在提交给
ExecutorService
的任务中,printNumbers()
方法被调用两次。 当任务被调用toshutdownNow()
中断时,对该方法的第一次调用提前结束,然后执行到达第二次调用。 中断只被主线程调用一次。 在第一次执行期间,通过调用Thread.currentThread().interrupt()
将中断传达给printNumber()
方法的第二次执行。 因此,第二次执行也会在打印第一个数字后提前结束。 不保留中断状态会导致方法的第二次执行完全运行 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; } } }
我尝试运行上面的代码并打印:
01230
当我评论Thread.currentThread().interrupt();
从catch
块,它打印:
01230123456789
虽然我觉得我在代码之前理解了解释,但我认为我不明白为什么解释是正确的,部分原因是我在官方文档中没有找到任何相关的解释/句子。 以下是我的具体疑问:
这是否意味着我们需要将Runnable
每个方法调用提交给ExecutorService.submit()
来捕获InterruptedException
并在其中调用Thread.currentThread().interrupt()
以便整个Runnable
主体进行中断? 如果我们在Runnable
任何方法调用中错过捕获InterruptedException
并调用Thread.currentThread().interrupt()
,那么它不会被中断?
如果以上是正确的,为什么Thread.interrupt()
的文档中没有这样的解释?
是不是如果我们想在任何方法的中断上做任何关闭任务,那么我们应该只捕获InterruptedException
,执行任何要完成的任务然后调用Thread.currentThread().interrupt()
?
如果问题 3 的答案是肯定的,那么我们应该什么时候做关闭任务? 我觉得任何关闭任务都应该在Thread.currentThread().interrupt()
之前完成,但在给定的例子中, break
在Thread.currentThread().interrupt()
之后被调用。
对问题4多想了几句,感觉线程中断是怎么处理的,没看清楚。 早些时候我觉得被中断的线程会立即被杀死,但情况似乎并非如此。 线程中断是如何发生的? 是否有任何 oracle 官方链接解释相同?
线程中断不是立即杀死线程的事情。 这是线程结束自己的建议。
线程中的代码不会自动终止。 所以你的代码应该通过优雅地终止来响应线程中断。 一种好的做法是在每次使用时重置中断标志,方法是调用Thread.currentThread().interrupt()
。 遵循状态的操作将抛出 InterruptedException,并且不会继续。
Thread.currentThread().interrupt();
应该是 catch 块中的第一条语句,以确保任何清理不会再次阻塞。 (使用 finally 进行清理也是一个好主意)
在那种特定情况下,我会将 try / catch 拉出 for 循环。
它基本上是线程结束自身的建议。
中断操作发生在调用 Thread.interrupt 时,以及定义为依次调用它的方法,例如 ThreadGroup.interrupt。
设 t 为调用 u.interrupt 的线程,对于某些线程 u,其中 t 和 u 可能相同。 此操作会导致您的中断状态设置为 true。
此外,如果存在某个对象 m 的等待集包含 u,则将 u 从 m 的等待集中删除。 这使您能够在等待操作中恢复,在这种情况下,此等待将在重新锁定 m 的监视器后抛出 InterruptedException。
调用 Thread.isInterrupted 可以确定线程的中断状态。 线程可以调用静态方法 Thread.interrupted 来观察和清除自己的中断状态。
——
中断状态标志 中断机制是使用称为中断状态的内部标志实现的。 调用 Thread.interrupt 设置此标志。 当线程通过调用静态方法 Thread.interrupted 检查中断时,中断状态被清除。 一个线程使用非静态 isInterrupted 方法来查询另一个线程的中断状态,它不会更改中断状态标志。
按照惯例,任何通过抛出 InterruptedException 退出的方法都会在它这样做时清除中断状态。 然而,中断状态总是有可能被另一个调用中断的线程立即再次设置。
对线程中断的最大误解是它是开箱即用的。 是的,设置线程中断标志的基本机制可以用来处理线程中断,但是线程应该如何做到这一点仍然取决于开发人员。 参见 ExecutorService shutdownNow
方法的注释:
除了尽力尝试停止处理正在执行的任务之外,没有任何保证。 例如,典型的实现将通过 Thread.interrupt 取消,因此任何未能响应中断的任务可能永远不会终止。
例如:
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);
}
}
}
虽然执行程序被关闭并且线程被中断,但它打印:
01234567890123456789
这是因为没有任何线程中断处理。
例如,如果您要使用Thread.interrupted()
测试线程是否被中断,那么您实际上可以处理线程内的中断。
要在第一个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);
}
}
}
这将执行第一个printNumbers
并打印:
0123456789
您的示例的问题在于,无论何时捕获InterruptedException
,线程的中断标志都会重置为false
。
因此,如果您要执行:
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;
}
}
}
}
这将中断对printNumbers
的第一次调用,但由于捕获了InterruptedException
,线程不再被视为中断,因此printNumbers
的第二次执行将继续并且sleep
不再被中断。 输出将是:
0120123456789
但是,当您在捕获后手动设置中断标志时,在第二次执行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;
}
}
}
}
它从第一次printNumbers
执行中打印012
,从第二次printNumbers
执行中打印0
:
0120
但请记住,这只是因为您正在手动实现线程中断处理,在这种情况下是由于使用Thread.sleep
。 如最后一个示例所示:
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);
}
}
}
这将打印出:
0120123456789
这是因为printNumbersNotInterruptible
不处理线程中断并因此完成其完整执行。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.