簡體   English   中英

Thread.interrupt() 是邪惡的嗎?

[英]Is Thread.interrupt() evil?

一位隊友提出了以下主張:

Thread.interrupt()本質上是壞的,應該(幾乎)永遠不會被使用”。

我試圖理解為什么會這樣。

從不使用Thread.interrupt()是已知的最佳實踐嗎? 你能提供證據為什么它壞了/有問題,不應該用於編寫健壯的多線程代碼嗎?

注意- 如果設計防腐劑“漂亮”,我對這個問題不感興趣。 我的問題是 - 有問題嗎?

簡短版本:

從不使用 Thread.interrupt() 是已知的最佳實踐嗎?

沒有。

你能提供證據為什么它是壞的/錯誤的,不應該用於編寫健壯的多線程代碼嗎?

反之亦然:它對於多線程代碼至關重要。

有關示例,請參見Java 並發實踐中的代碼清單 7.7。

更長的版本:

在這里,我們在一個特定的地方使用這個方法:處理InterruptedExceptions 這可能看起來有點奇怪,但代碼如下:

try {
    // Some code that might throw an InterruptedException.  
    // Using sleep as an example
    Thread.sleep(10000);
} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    Thread.currentThread().interrupt();
}

這為我們做了兩件事:

  1. 它避免了吃中斷異常。 IDE 自動異常處理程序總是為您提供類似ie.printStackTrace();東西ie.printStackTrace(); 和一個活潑的“TODO:這里需要一些有用的東西!” 評論。
  2. 它在不強制此方法上檢查異常的情況下恢復中斷狀態。 如果您正在實現的方法簽名沒有throws InterruptedException子句,則這是傳播該中斷狀態的另一種選擇。

一位評論者建議我應該使用未經檢查的異常“強制線程死亡”。 這是假設我事先知道突然終止線程是正確的做法。 我不知道。

在上面引用的清單之前的頁面上從 JCIP 引用 Brian Goetz:

任務不應對其執行線程的中斷策略進行任何假設,除非它被明確設計為在具有特定中斷策略的服務中運行。

例如,假設我這樣做了:

} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    // The following is very rude.
    throw new RuntimeException("I think the thread should die immediately", ie);
}

我要聲明的是,不管調用堆棧的其余部分和相關狀態的其他義務如何,這個線程現在需要死亡。 我會試圖繞過所有其他 catch 塊和狀態清理代碼,直接進入線程死亡。 更糟糕的是,我會消耗線程的中斷狀態。 上游邏輯現在必須解構我的異常,以試圖弄清楚是否存在程序邏輯錯誤,或者我是否試圖將已檢查的異常隱藏在一個模糊的包裝器中。

例如,以下是團隊中的其他人必須立即執行的操作:

try {
    callBobsCode();
} catch (RuntimeException e) { // Because Bob is a jerk
    if (e.getCause() instanceOf InterruptedException) {
        // Man, what is that guy's problem?
        interruptCleanlyAndPreserveState();
        // Restoring the interrupt status
        Thread.currentThread().interrupt();
    }
}

中斷狀態比任何特定的InterruptException更重要。 有關原因的具體示例,請參閱Thread.interrupt()的 javadoc:

如果此線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法或 join()、join(long)、join(long, int) 方法時被阻塞、sleep(long) 或 sleep(long, int) 等方法,那么它的中斷狀態將被清除,並且它會收到一個 InterruptedException。

如您所見,在處理中斷請求時可以創建和處理多個 InterruptedException,但前提是該中斷狀態被保留。

我知道Thread.interrupt()被破壞的唯一方法是它實際上並沒有做它看起來可能的事情 - 它實際上只能中斷偵聽它的代碼。

但是,如果使用得當,在我看來它是一個很好的任務管理和取消的內置機制。

我推薦Java Concurrency in Practice以閱讀有關正確和安全使用它的更多信息。

Thread.interrupt()的主要問題是大多數程序員不知道隱藏的陷阱並以錯誤的方式使用它。 例如,當您處理中斷時,有一些方法可以清除標志(因此狀態會丟失)。

此外,調用不會總是立即中斷線程。 例如,當它掛在某個系統例程中時,什么都不會發生。 事實上,如果線程不檢查標志並且從不調用拋出InterruptException的 Java 方法,則中斷它不會有任何影響。

不,這不是馬車。 它實際上是您如何在 Java 中停止線程的基礎。 它在執行器框架中使用從java.util.concurrent中-見實施java.util.concurrent.FutureTask.Sync.innerCancel

至於失敗,我從未見過失敗,我廣泛使用它。

未提及的原因之一是中斷信號可能會丟失,這使得調用 Thread.interrupt() 方法毫無意義。 因此,除非 Thread.run() 方法中的代碼在 while 循環中旋轉,否則調用 Thread.interrupt() 的結果是不確定的。

我注意到,在線程ONE ,當沒有可用的數據庫連接時,我執行DriverManager.getConnection() (比如服務器關閉,因此最后這一行拋出SQLException )並且從線程TWO我明確調用ONE.interrupt() ,然后兩個ONE.interrupted()ONE.isInterrupted()即使放在處理SQLExceptioncatch{}塊中的第一行,也會返回false

當然,我通過實現額外的信號量解決了這個問題,但這很麻煩,因為這是我 15 年 Java 開發中的第一個此類問題。

我想這是因為com.microsoft.sqlserver.jdbc.SQLServerDriver的錯誤。 而且我進行了更多調查以確認對本native函數的調用在它自己產生的所有情況下都會消耗此中斷,但在成功時保留它。

托梅克

PS我發現了類似的問題

PPS 我附上一個非常簡短的例子,說明我在上面寫的內容。 注冊的類可以在sqljdbc42.jar找到。 我在 2015 年 8 月 20 日構建的類中發現了這個錯誤,然后我更新到了可用的最新版本(從 2017 年 1 月 12 日開始),但該錯誤仍然存​​在。

import java.sql.*;

public class TEST implements Runnable{
    static{
        try{
//register the proper driver
           Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        }
        catch(ClassNotFoundException e){
            System.err.println("Cannot load JDBC driver (not found in CLASSPATH).");
        }
    }

    public void run() {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
//prints true
        try{
            Connection conn = DriverManager.getConnection("jdbc:sqlserver://xxxx\\xxxx;databaseName=xxxx;integratedSecurity=true");
        }
        catch (SQLException e){
            System.out.println(e.getMessage());
        }
        System.out.println(Thread.currentThread().isInterrupted());
//prints false
        System.exit(0);
    }

    public static void main(String[] args){
        (new Thread(new TEST())).start();
    }
}

如果您將一些完全不正確的內容作為"foo"傳遞給DriverManager.getConnection() ,您將獲得消息“找不到適合 foo 的驅動程序”,並且第二個打印輸出仍將如預期的那樣正確。 但是,如果您傳遞了正確構建的字符串,但是,例如,您的服務器已關閉或您丟失了網絡連接(這通常發生在生產環境中),您將看到java.net套接字超時錯誤打印輸出和線程的interrupted()狀態丟失。

問題不在於實現沒有錯誤,而是您的線程在被中斷時處於未知狀態,這可能導致不可預測的行為。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM