簡體   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