欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java處理InterruptedException異常的理論與實踐

 更新時間:2016年08月10日 11:51:37   投稿:daisy  
在使用Java的過程中,有個情景或許很多人見過,您在編寫一個測試程序,程序需要暫停一段時間,于是調(diào)用 Thread.sleep()。但是編譯器或 IDE 報錯說沒有處理檢查到的 InterruptedException。InterruptedException 是什么呢,為什么必須處理它?下面跟著小編一起來看看。

前言

對于 InterruptedException,一種常見的處理方式是 “生吞(swallow)” 它 —— 捕捉它,然后什么也不做(或者記錄下它,不過這也好不到哪去)—— 就像后面的 清單 4 一樣。不幸的是,這種方法忽略了這樣一個事實:這期間可能發(fā)生中斷,而中斷可能導(dǎo)致應(yīng)用程序喪失及時取消活動或關(guān)閉的能力。

阻塞方法

當(dāng)一個方法拋出 InterruptedException 時,它不僅告訴您它可以拋出一個特定的檢查異常,而且還告訴您其他一些事情。例如,它告訴您它是一個阻塞(blocking)方法,如果您響應(yīng)得當(dāng)?shù)脑?,它將嘗試消除阻塞并盡早返回。

阻塞方法不同于一般的要運(yùn)行較長時間的方法。一般方法的完成只取決于它所要做的事情,以及是否有足夠多可用的計算資源(CPU 周期和內(nèi)存)。而阻塞方法的完成還取決于一些外部的事件,例如計時器到期,I/O 完成,或者另一個線程的動作(釋放一個鎖,設(shè)置一個標(biāo)志,或者將一個任務(wù)放在一個工作隊列中)。一般方法在它們的工作做完后即可結(jié)束,而阻塞方法較難于預(yù)測,因為它們?nèi)Q于外部事件。阻塞方法可能影響響應(yīng)能力,因為難于預(yù)測它們何時會結(jié)束。

阻塞方法可能因為等不到所等的事件而無法終止,因此令阻塞方法可取消 就非常有用(如果長時間運(yùn)行的非阻塞方法是可取消的,那么通常也非常有用)??扇∠僮魇侵改軓耐獠渴怪谡M瓿芍敖K止的操作。由 Thread 提供并受 Thread.sleep() 和 Object.wait() 支持的中斷機(jī)制就是一種取消機(jī)制;它允許一個線程請求另一個線程停止它正在做的事情。當(dāng)一個方法拋出 InterruptedException 時,它是在告訴您,如果執(zhí)行該方法的線程被中斷,它將嘗試停止它正在做的事情而提前返回,并通過拋出 InterruptedException 表明它提前返回。 行為良好的阻塞庫方法應(yīng)該能對中斷作出響應(yīng)并拋出 InterruptedException,以便能夠用于可取消活動中,而不至于影響響應(yīng)。

線程中斷

每個線程都有一個與之相關(guān)聯(lián)的 Boolean 屬性,用于表示線程的中斷狀態(tài)(interrupted status)。中斷狀態(tài)初始時為 false;當(dāng)另一個線程通過調(diào)用 Thread.interrupt() 中斷一個線程時,會出現(xiàn)以下兩種情況之一。如果那個線程在執(zhí)行一個低級可中斷阻塞方法,例如 Thread.sleep()、 Thread.join() 或 Object.wait(),那么它將取消阻塞并拋出 InterruptedException。否則, interrupt() 只是設(shè)置線程的中斷狀態(tài)。 在被中斷線程中運(yùn)行的代碼以后可以輪詢中斷狀態(tài),看看它是否被請求停止正在做的事情。中斷狀態(tài)可以通過 Thread.isInterrupted() 來讀取,并且可以通過一個名為 Thread.interrupted() 的操作讀取和清除。

中斷是一種協(xié)作機(jī)制。當(dāng)一個線程中斷另一個線程時,被中斷的線程不一定要立即停止正在做的事情。相反,中斷是禮貌地請求另一個線程在它愿意并且方便的時候停止它正在做的事情。有些方法,例如 Thread.sleep(),很認(rèn)真地對待這樣的請求,但每個方法不是一定要對中斷作出響應(yīng)。對于中斷請求,不阻塞但是仍然要花較長時間執(zhí)行的方法可以輪詢中斷狀態(tài),并在被中斷的時候提前返回。 您可以隨意忽略中斷請求,但是這樣做的話會影響響應(yīng)。

中斷的協(xié)作特性所帶來的一個好處是,它為安全地構(gòu)造可取消活動提供更大的靈活性。我們很少希望一個活動立即停止;如果活動在正在進(jìn)行更新的時候被取消,那么程序數(shù)據(jù)結(jié)構(gòu)可能處于不一致狀態(tài)。中斷允許一個可取消活動來清理正在進(jìn)行的工作,恢復(fù)不變量,通知其他活動它要被取消,然后才終止。

處理 InterruptedException

如果拋出 InterruptedException 意味著一個方法是阻塞方法,那么調(diào)用一個阻塞方法則意味著您的方法也是一個阻塞方法,而且您應(yīng)該有某種策略來處理 InterruptedException。通常最容易的策略是自己拋出 InterruptedException,如清單 1 中 putTask() 和 getTask() 方法中的代碼所示。 這樣做可以使方法對中斷作出響應(yīng),并且只需將 InterruptedException 添加到 throws 子句。

清單 1. 不捕捉 InterruptedException,將它傳播給調(diào)用者

public class TaskQueue {
  private static final int MAX_TASKS = 1000;

  private BlockingQueue<Task> queue 
    = new LinkedBlockingQueue<Task>(MAX_TASKS);

  public void putTask(Task r) throws InterruptedException { 
    queue.put(r);
  }

  public Task getTask() throws InterruptedException { 
    return queue.take();
  }
}

有時候需要在傳播異常之前進(jìn)行一些清理工作。在這種情況下,可以捕捉 InterruptedException,執(zhí)行清理,然后拋出異常。清單 2 演示了這種技術(shù),該代碼是用于匹配在線游戲服務(wù)中的玩家的一種機(jī)制。 matchPlayers() 方法等待兩個玩家到來,然后開始一個新游戲。如果在一個玩家已到來,但是另一個玩家仍未到來之際該方法被中斷,那么它會將那個玩家放回隊列中,然后重新拋出 InterruptedException,這樣那個玩家對游戲的請求就不至于丟失。

清單 2. 在重新拋出 InterruptedException 之前執(zhí)行特定于任務(wù)的清理工作

public class PlayerMatcher {
  private PlayerSource players;

  public PlayerMatcher(PlayerSource players) { 
    this.players = players; 
  }

  public void matchPlayers() throws InterruptedException { 
    try {
       Player playerOne, playerTwo;
       while (true) {
         playerOne = playerTwo = null;
         // Wait for two players to arrive and start a new game
         playerOne = players.waitForPlayer(); // could throw IE
         playerTwo = players.waitForPlayer(); // could throw IE
         startNewGame(playerOne, playerTwo);
       }
     }
     catch (InterruptedException e) { 
       // If we got one player and were interrupted, put that player back
       if (playerOne != null)
         players.addFirst(playerOne);
       // Then propagate the exception
       throw e;
     }
  }
}

不要生吞中斷

有時候拋出 InterruptedException 并不合適,例如當(dāng)由 Runnable 定義的任務(wù)調(diào)用一個可中斷的方法時,就是如此。在這種情況下,不能重新拋出 InterruptedException,但是您也不想什么都不做。當(dāng)一個阻塞方法檢測到中斷并拋出 InterruptedException 時,它清除中斷狀態(tài)。如果捕捉到 InterruptedException 但是不能重新拋出它,那么應(yīng)該保留中斷發(fā)生的證據(jù),以便調(diào)用棧中更高層的代碼能知道中斷,并對中斷作出響應(yīng)。該任務(wù)可以通過調(diào)用 interrupt() 以 “重新中斷” 當(dāng)前線程來完成,如清單 3 所示。至少,每當(dāng)捕捉到 InterruptedException 并且不重新拋出它時,就在返回之前重新中斷當(dāng)前線程。

清單 3. 捕捉 InterruptedException 后恢復(fù)中斷狀態(tài)

public class TaskRunner implements Runnable {
  private BlockingQueue<Task> queue;

  public TaskRunner(BlockingQueue<Task> queue) { 
    this.queue = queue; 
  }

  public void run() { 
    try {
       while (true) {
         Task task = queue.take(10, TimeUnit.SECONDS);
         task.execute();
       }
     }
     catch (InterruptedException e) { 
       // Restore the interrupted status
       Thread.currentThread().interrupt();
     }
  }
}

處理 InterruptedException 時采取的最糟糕的做法是生吞它 —— 捕捉它,然后既不重新拋出它,也不重新斷言線程的中斷狀態(tài)。對于不知如何處理的異常,最標(biāo)準(zhǔn)的處理方法是捕捉它,然后記錄下它,但是這種方法仍然無異于生吞中斷,因為調(diào)用棧中更高層的代碼還是無法獲得關(guān)于該異常的信息。(僅僅記錄 InterruptedException 也不是明智的做法,因為等到人來讀取日志的時候,再來對它作出處理就為時已晚了。) 清單 4 展示了一種使用得很廣泛的模式,這也是生吞中斷的一種模式:

清單 4. 生吞中斷 —— 不要這么做

// Don't do this 
public class TaskRunner implements Runnable {
  private BlockingQueue<Task> queue;

  public TaskRunner(BlockingQueue<Task> queue) { 
    this.queue = queue; 
  }

  public void run() { 
    try {
       while (true) {
         Task task = queue.take(10, TimeUnit.SECONDS);
         task.execute();
       }
     }
     catch (InterruptedException swallowed) { 
       /* DON'T DO THIS - RESTORE THE INTERRUPTED STATUS INSTEAD */
     }
  }
}

如果不能重新拋出 InterruptedException,不管您是否計劃處理中斷請求,仍然需要重新中斷當(dāng)前線程,因為一個中斷請求可能有多個 “接收者”。標(biāo)準(zhǔn)線程池 (ThreadPoolExecutor)worker 線程實現(xiàn)負(fù)責(zé)中斷,因此中斷一個運(yùn)行在線程池中的任務(wù)可以起到雙重效果,一是取消任務(wù),二是通知執(zhí)行線程線程池正要關(guān)閉。如果任務(wù)生吞中斷請求,則 worker 線程將不知道有一個被請求的中斷,從而耽誤應(yīng)用程序或服務(wù)的關(guān)閉。

實現(xiàn)可取消任務(wù)

語言規(guī)范中并沒有為中斷提供特定的語義,但是在較大的程序中,難于維護(hù)除取消外的任何中斷語義。取決于是什么活動,用戶可以通過一個 GUI 或通過網(wǎng)絡(luò)機(jī)制,例如 JMX 或 Web 服務(wù)來請求取消。程序邏輯也可以請求取消。例如,一個 Web 爬行器(crawler)如果檢測到磁盤已滿,它會自動關(guān)閉自己,否則一個并行算法會啟動多個線程來搜索解決方案空間的不同區(qū)域,一旦其中一個線程找到一個解決方案,就取消那些線程。

僅僅因為一個任務(wù)是可取消的,并不意味著需要立即 對中斷請求作出響應(yīng)。對于執(zhí)行一個循環(huán)中的代碼的任務(wù),通常只需為每一個循環(huán)迭代檢查一次中斷。取決于循環(huán)執(zhí)行的時間有多長,任何代碼可能要花一些時間才能注意到線程已經(jīng)被中斷(或者是通過調(diào)用 Thread.isInterrupted() 方法輪詢中斷狀態(tài),或者是調(diào)用一個阻塞方法)。 如果任務(wù)需要提高響應(yīng)能力,那么它可以更頻繁地輪詢中斷狀態(tài)。阻塞方法通常在入口就立即輪詢中斷狀態(tài),并且,如果它被設(shè)置來改善響應(yīng)能力,那么還會拋出 InterruptedException。

惟一可以生吞中斷的時候是您知道線程正要退出。只有當(dāng)調(diào)用可中斷方法的類是 Thread 的一部分,而不是 Runnable 或通用庫代碼的情況下,才會發(fā)生這樣的場景,清單 5 演示了這種情況。清單 5 創(chuàng)建一個線程,該線程列舉素數(shù),直到被中斷,這里還允許該線程在被中斷時退出。用于搜索素數(shù)的循環(huán)在兩個地方檢查是否有中斷:一處是在 while 循環(huán)的頭部輪詢 isInterrupted() 方法,另一處是調(diào)用阻塞方法 BlockingQueue.put()。

清單 5. 如果知道線程正要退出的話,則可以生吞中斷

public class PrimeProducer extends Thread {
  private final BlockingQueue<BigInteger> queue;

  PrimeProducer(BlockingQueue<BigInteger> queue) {
    this.queue = queue;
  }

  public void run() {
    try {
      BigInteger p = BigInteger.ONE;
      while (!Thread.currentThread().isInterrupted())
        queue.put(p = p.nextProbablePrime());
    } catch (InterruptedException consumed) {
      /* Allow thread to exit */
    }
  }

  public void cancel() { interrupt(); }
}

不可中斷的阻塞方法

并非所有的阻塞方法都拋出 InterruptedException。輸入和輸出流類會阻塞等待 I/O 完成,但是它們不拋出 InterruptedException,而且在被中斷的情況下也不會提前返回。然而,對于套接字 I/O,如果一個線程關(guān)閉套接字,則那個套接字上的阻塞 I/O 操作將提前結(jié)束,并拋出一個 SocketException。java.nio 中的非阻塞 I/O 類也不支持可中斷 I/O,但是同樣可以通過關(guān)閉通道或者請求 Selector 上的喚醒來取消阻塞操作。類似地,嘗試獲取一個內(nèi)部鎖的操作(進(jìn)入一個 synchronized 塊)是不能被中斷的,但是 ReentrantLock 支持可中斷的獲取模式。

不可取消的任務(wù)

有些任務(wù)拒絕被中斷,這使得它們是不可取消的。但是,即使是不可取消的任務(wù)也應(yīng)該嘗試保留中斷狀態(tài),以防在不可取消的任務(wù)結(jié)束之后,調(diào)用棧上更高層的代碼需要對中斷進(jìn)行處理。清單 6 展示了一個方法,該方法等待一個阻塞隊列,直到隊列中出現(xiàn)一個可用項目,而不管它是否被中斷。為了方便他人,它在結(jié)束后在一個 finally 塊中恢復(fù)中斷狀態(tài),以免剝奪中斷請求的調(diào)用者的權(quán)利。(它不能在更早的時候恢復(fù)中斷狀態(tài),因為那將導(dǎo)致無限循環(huán) —— BlockingQueue.take() 將在入口處立即輪詢中斷狀態(tài),并且,如果發(fā)現(xiàn)中斷狀態(tài)集,就會拋出 InterruptedException。)

清單 6. 在返回前恢復(fù)中斷狀態(tài)的不可取消任務(wù)

public Task getNextTask(BlockingQueue<Task> queue) {
  boolean interrupted = false;
  try {
    while (true) {
      try {
        return queue.take();
      } catch (InterruptedException e) {
        interrupted = true;
        // fall through and retry
      }
    }
  } finally {
    if (interrupted)
      Thread.currentThread().interrupt();
  }
}

總結(jié)

您可以用 Java 平臺提供的協(xié)作中斷機(jī)制來構(gòu)造靈活的取消策略。各活動可以自行決定它們是可取消的還是不可取消的,以及如何對中斷作出響應(yīng),如果立即返回會危害應(yīng)用程序完整性的話,它們還可以推遲中斷。即使您想在代碼中完全忽略中斷,也應(yīng)該確保在捕捉到 InterruptedException 但是沒有重新拋出它的情況下,恢復(fù)中斷狀態(tài),以免調(diào)用它的代碼無法獲知中斷的發(fā)生。以上就是Java處理InterruptedException異常的理論與實踐的全部內(nèi)容,希望本文對大家有所幫助,如果有疑問歡迎大家留言進(jìn)行討論。

相關(guān)文章

  • java 線性表接口的實例詳解

    java 線性表接口的實例詳解

    這篇文章主要介紹了java 線性表接口的實現(xiàn)實例詳解的相關(guān)資料,希望通過本能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • Java都有哪些創(chuàng)建線程的方法

    Java都有哪些創(chuàng)建線程的方法

    這篇文章主要介紹了Java都有哪些創(chuàng)建線程的方法,文章分享Java創(chuàng)建線程得幾種方法及推薦使用哪種方法,下面詳細(xì)內(nèi)容需要的小伙伴可以參考一下
    2022-05-05
  • 金三銀四復(fù)工高頻面試題java算法LeetCode396旋轉(zhuǎn)函數(shù)

    金三銀四復(fù)工高頻面試題java算法LeetCode396旋轉(zhuǎn)函數(shù)

    這篇文章主要為大家介紹了金三銀四復(fù)工高頻面試題之java算法題解LeetCode396旋轉(zhuǎn)函數(shù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • 詳解如何更改SpringBoot TomCat運(yùn)行方式

    詳解如何更改SpringBoot TomCat運(yùn)行方式

    這篇文章主要介紹了詳解如何更改SpringBoot TomCat運(yùn)行方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Javacsv實現(xiàn)Java讀寫csv文件

    Javacsv實現(xiàn)Java讀寫csv文件

    這篇文章主要為大家詳細(xì)介紹了Javacsv實現(xiàn)Java讀寫csv文件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • Spring Cloud應(yīng)用實現(xiàn)配置自動刷新過程詳解

    Spring Cloud應(yīng)用實現(xiàn)配置自動刷新過程詳解

    這篇文章主要介紹了Spring Cloud應(yīng)用實現(xiàn)配置自動刷新過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • Java掃描文件夾下所有文件名

    Java掃描文件夾下所有文件名

    這篇文章主要為大家詳細(xì)介紹了Java掃描文件夾下所有文件名,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • Springboot通過配置WebMvcConfig處理Cors非同源訪問跨域問題

    Springboot通過配置WebMvcConfig處理Cors非同源訪問跨域問題

    這篇文章主要介紹了Springboot通過配置WebMvcConfig處理Cors非同源訪問跨域問題,關(guān)于Cors跨域的問題,前端有代理和jsonp的常用方式解決這種非同源的訪問拒絕策略
    2023-04-04
  • Java泛型的簡單實例

    Java泛型的簡單實例

    這篇文章介紹了Java泛型的簡單實例,有需要的朋友可以參考一下
    2013-10-10
  • RabbitMQ?延遲隊列實現(xiàn)訂單支付結(jié)果異步階梯性通知(實例代碼)

    RabbitMQ?延遲隊列實現(xiàn)訂單支付結(jié)果異步階梯性通知(實例代碼)

    這篇文章主要介紹了RabbitMQ?延遲隊列實現(xiàn)訂單支付結(jié)果異步階梯性通知,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-02-02

最新評論