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