繁体   English   中英

理解 Java 中的线程中断

[英]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

虽然我觉得我在代码之前理解了解释,但我认为我不明白为什么解释是正确的,部分原因是我在官方文档中没有找到任何相关的解释/句子。 以下是我的具体疑问:

  1. 这是否意味着我们需要将Runnable每个方法调用提交给ExecutorService.submit()来捕获InterruptedException并在其中调用Thread.currentThread().interrupt()以便整个Runnable主体进行中断? 如果我们在Runnable任何方法调用中错过捕获InterruptedException并调用Thread.currentThread().interrupt() ,那么它不会被中断?

  2. 如果以上是正确的,为什么Thread.interrupt()文档中没有这样的解释?

  3. 是不是如果我们想在任何方法的中断上做任何关闭任务,那么我们应该只捕获InterruptedException ,执行任何要完成的任务然后调用Thread.currentThread().interrupt()

  4. 如果问题 3 的答案是肯定的,那么我们应该什么时候做关闭任务? 我觉得任何关闭任务都应该在Thread.currentThread().interrupt()之前完成,但在给定的例子中, breakThread.currentThread().interrupt()之后被调用。

  5. 对问题4多想了几句,感觉线程中断是怎么处理的,没看清楚。 早些时候我觉得被中断的线程会立即被杀死,但情况似乎并非如此。 线程中断是如何发生的? 是否有任何 oracle 官方链接解释相同?

线程中断不是立即杀死线程的事情。 这是线程结束自己的建议。

  1. 线程中的代码不会自动终止。 所以你的代码应该通过优雅地终止来响应线程中断。 一种好的做法是在每次使用时重置中断标志,方法是调用Thread.currentThread().interrupt() 遵循状态的操作将抛出 InterruptedException,并且不会继续。

  2. Thread.currentThread().interrupt(); 应该是 catch 块中的第一条语句,以确保任何清理不会再次阻塞。 (使用 finally 进行清理也是一个好主意)

  3. 在那种特定情况下,我会将 try / catch 拉出 for 循环。

  4. 它基本上是线程结束自身的建议。

Java 语言规范 17.2.3

中断操作发生在调用 Thread.interrupt 时,以及定义为依次调用它的方法,例如 ThreadGroup.interrupt。

设 t 为调用 u.interrupt 的线程,对于某些线程 u,其中 t 和 u 可能相同。 此操作会导致您的中断状态设置为 true。

此外,如果存在某个对象 m 的等待集包含 u,则将 u 从 m 的等待集中删除。 这使您能够在等待操作中恢复,在这种情况下,此等待将在重新锁定 m 的监视器后抛出 InterruptedException。

调用 Thread.isInterrupted 可以确定线程的中断状态。 线程可以调用静态方法 Thread.interrupted 来观察和清除自己的中断状态。

——

Oracle 中断教程

中断状态标志 中断机制是使用称为中断状态的内部标志实现的。 调用 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.

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