溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java中斷異常的正確處理方法

發(fā)布時(shí)間:2020-08-27 20:03:12 來(lái)源:腳本之家 閱讀:193 作者:java歐陽(yáng)豐 欄目:編程語(yǔ)言

處理InterruptedException

這個(gè)故事可能很熟悉:你正在寫(xiě)一個(gè)測(cè)試程序,你需要暫停某個(gè)線程一段時(shí)間,所以你調(diào)用 Thread.sleep()。然后編譯器或 IDE 就會(huì)抱怨說(shuō) InterruptedException 沒(méi)有拋出聲明或捕獲。什么是 InterruptedException,你為什么要處理它?

最常見(jiàn)的響應(yīng) InterruptedException 做法是吞下它 - 捕獲它并且什么也不做(或者記錄它,也沒(méi)好多少) - 正如我們將在清單4中看到的那樣。不幸的是,這種方法拋棄了關(guān)于​​中斷發(fā)生的重要信息,這可能會(huì)損害應(yīng)用程序取消活動(dòng)或響應(yīng)及時(shí)關(guān)閉的能力。

阻塞方法

當(dāng)一個(gè)方法拋出 InterruptedException 時(shí),意味著幾件事情: 除了它可以?huà)伋鲆粋€(gè)特定的檢查異常, 它還告訴你它是一種阻塞方法,它會(huì)嘗試解除阻塞并提前返回。

阻塞方法不同于僅需要很長(zhǎng)時(shí)間才能運(yùn)行完成的普通方法。普通方法的完成僅取決于你要求它做多少事以及是否有足夠的計(jì)算資源(CPU周期和內(nèi)存)。另一方面,阻塞方法的完成還取決于某些外部事件,例如計(jì)時(shí)器到期,I/O 完成或另一個(gè)線程的操作(釋放鎖,設(shè)置標(biāo)志或放置任務(wù)到工作隊(duì)列)。普通方法可以在完成工作后立即結(jié)束,但阻塞方法不太好預(yù)測(cè),因?yàn)樗鼈円蕾?lài)于外部事件。

因?yàn)槿绻麄冋诘却肋h(yuǎn)不會(huì)在事件,發(fā)生堵塞的方法有可能永遠(yuǎn)不結(jié)束,常用在阻塞可取消的操作。對(duì)于長(zhǎng)時(shí)間運(yùn)行的非阻塞方法,通常也是可以取消的??扇∠僮魇强梢栽谕ǔW孕型瓿芍皬耐獠繌?qiáng)制移動(dòng)到完成狀態(tài)的操作。 Thread提供的Thread.sleep() 和 Object.wait() 方法中斷機(jī)制是一種取消線程繼續(xù)阻塞的機(jī)制; 它允許一個(gè)線程請(qǐng)求另一個(gè)線程提前停止它正在做的事情。當(dāng)一個(gè)方法拋出時(shí) InterruptedException,它告訴你如果執(zhí)行方法的線程被中斷,它將嘗試停止它正在做的事情提前返回, 并通過(guò)拋出 InterruptedException 表明它的提早返回。表現(xiàn)良好的阻塞庫(kù)方法應(yīng)該響應(yīng)中斷并拋出 InterruptedException 異常, 以便它們可以應(yīng)用在可取消的活動(dòng)中而不會(huì)妨礙程序的響應(yīng)性。

線程中斷

每個(gè)線程都有一個(gè)與之關(guān)聯(lián)的布爾屬性,表示其中斷狀態(tài)。中斷狀態(tài)最初為假; 當(dāng)某個(gè)線程被其他線程通過(guò)調(diào)用中斷 Thread.interrupt() 時(shí), 會(huì)發(fā)生以下兩種情況之一: 如果該線程正在執(zhí)行低級(jí)別的中斷阻塞方法 Thread.sleep(),Thread.join()或 Object.wait()等,它取消阻塞并拋出 InterruptedException。除此以外,interrupt() 僅設(shè)置線程的中斷狀態(tài)。在中斷的線程中運(yùn)行的代碼可以稍后輪詢(xún)中斷的狀態(tài)以查看是否已經(jīng)請(qǐng)求停止它正在做的事情; 中斷狀態(tài)可以通過(guò) Thread.isInterrupted() 讀取,并且可以在命名不佳的單個(gè)操作Thread.interrupted()中讀取和清除 。

中斷是一種合作機(jī)制。當(dāng)一個(gè)線程中斷另一個(gè)線程時(shí),被中斷的線程不一定會(huì)立即停止它正在做的事情。相反,中斷是一種禮貌地要求另一個(gè)線程在方便的時(shí)候停止它正在做什么的方式。有些方法,比如Thread.sleep()認(rèn)真對(duì)待這個(gè)請(qǐng)求,但方法不一定要注意中斷請(qǐng)求。不阻塞但仍可能需要很長(zhǎng)時(shí)間才能執(zhí)行完成的方法可以通過(guò)輪詢(xún)中斷狀態(tài)來(lái)尊重中斷請(qǐng)求,并在中斷時(shí)提前返回。你可以自由地忽略中斷請(qǐng)求,但這樣做可能會(huì)影響響應(yīng)速度。

中斷的合作性質(zhì)的一個(gè)好處是它為安全地構(gòu)建可取消的活動(dòng)提供了更大的靈活性。我們很少想立即停止活動(dòng); 如果活動(dòng)在更新期間被取消,程序數(shù)據(jù)結(jié)構(gòu)可能會(huì)處于不一致?tīng)顟B(tài)。中斷允許可取消活動(dòng)清理正在進(jìn)行的任何工作,恢復(fù)不變量,通知其他活動(dòng)取消事件,然后終止。

處理InterruptedException

如果 throw InterruptedException 意味著這個(gè)方法是一個(gè)阻塞方法,那么調(diào)用一個(gè)阻塞方法意味著你的方法也是一個(gè)阻塞方法,你應(yīng)該有一個(gè)處理策略 InterruptedException。通常最簡(jiǎn)單的策略是你自己也拋出 InterruptedException 異常,如清單1 中的 putTask() 和 getTask() 方法所示。這樣做會(huì)使你的方法響應(yīng)中斷,并且通常只需要添加 InterruptedException 到 throws 子句。

清單1.通過(guò)不捕獲它來(lái)向調(diào)用者傳播InterruptedException

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();
 }
}

有時(shí)在傳播異常之前需要進(jìn)行一些清理。在這種情況下,你可以捕獲 InterruptedException,執(zhí)行清理,然后重新拋出異常。清單2是一種用于匹配在線游戲服務(wù)中的玩家的機(jī)制,說(shuō)明了這種技術(shù)。該 matchPlayers() 方法等待兩個(gè)玩家到達(dá)然后開(kāi)始新游戲。如果在一個(gè)玩家到達(dá)之后但在第二個(gè)玩家到達(dá)之前它被中斷,則在重新投擲之前將該玩家放回隊(duì)列 InterruptedException,以便玩家的游戲請(qǐng)求不會(huì)丟失。

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

public class PlayerMatcher {
 private PlayerSource players;
 
 public PlayerMatcher(PlayerSource players) { 
  this.players = players; 
 }
 
 public void matchPlayers() <strong>throws InterruptedException</strong> { 
  Player playerOne, playerTwo;
   try {
    while (true) {
     playerOne = playerTwo = null;
     // 等待兩個(gè)玩家到來(lái)以便開(kāi)始游戲
     playerOne = players.waitForPlayer(); // 會(huì)拋出中斷異常
     playerTwo = players.waitForPlayer(); // 會(huì)拋出中斷異常
     startNewGame(playerOne, playerTwo);
    }
   }
   catch (InterruptedException e) { 
    // 如一個(gè)玩家中斷了, 將這個(gè)玩家放回隊(duì)列
    if (playerOne != null)
     players.addFirst(playerOne);
    // 然后傳播異常
    throw e;
   }
 }
}

不要吞下中斷

有時(shí)拋出 InterruptedException 不是一種選擇,例如當(dāng)通過(guò) Runnable 調(diào)用可中斷方法定義的任務(wù)時(shí)。在這種情況下,你不能重新拋出 InterruptedException,但你也不想做任何事情。當(dāng)阻塞方法檢測(cè)到中斷和拋出時(shí) InterruptedException,它會(huì)清除中斷狀態(tài)。如果你抓住 InterruptedException 但不能重新拋出它,你應(yīng)該保留中斷發(fā)生的證據(jù),以便調(diào)用堆棧上的代碼可以了解中斷并在需要時(shí)響應(yīng)它。此任務(wù)通過(guò)調(diào)用 interrupt()實(shí)現(xiàn)“重新中斷”當(dāng)前線程,如清單3所示。至少,無(wú)論何時(shí)捕獲 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) { 
    //重要: 恢復(fù)中斷狀態(tài)
    Thread.currentThread().interrupt();
   }
 }
}

你可以做的最糟糕的事情 InterruptedException 就是吞下它 - 抓住它,既不重新拋出它也不重新確定線程的中斷狀態(tài)。處理你沒(méi)有規(guī)劃的異常的標(biāo)準(zhǔn)方法 - 捕獲它并記錄它 - 也算作吞噬中斷,因?yàn)檎{(diào)用堆棧上的代碼將無(wú)法找到它。(記錄 InterruptedException 也很愚蠢,因?yàn)楫?dāng)人類(lèi)讀取日志時(shí),對(duì)它做任何事都為時(shí)已晚。)清單4顯示了吞下中斷的常見(jiàn)模式:

清單4.吞下中斷 - 不要這樣做

// 不要這么做!
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,無(wú)論你是否計(jì)劃對(duì)中斷請(qǐng)求執(zhí)行操作,你仍然希望重新中斷當(dāng)前線程,因?yàn)閱蝹€(gè)中斷請(qǐng)求可能有多個(gè)“收件人”。標(biāo)準(zhǔn)線程池(ThreadPoolExecutor)工作線程實(shí)現(xiàn)響應(yīng)中斷,因此中斷線程池中運(yùn)行的任務(wù)可能具有取消任務(wù)和通知執(zhí)行線程線程池正在關(guān)閉的效果。如果作業(yè)吞下中斷請(qǐng)求,則工作線程可能不會(huì)知道請(qǐng)求了中斷,這可能會(huì)延遲應(yīng)用程序或服務(wù)關(guān)閉。

實(shí)施可取消的任務(wù)

語(yǔ)言規(guī)范中沒(méi)有任何內(nèi)容給出任何特定語(yǔ)義的中斷,但在較大的程序中,除了取消之外,很難保持中斷的任何語(yǔ)義。根據(jù)活動(dòng),用戶(hù)可以通過(guò) GUI 或通過(guò) JMX 或 Web 服務(wù)等網(wǎng)絡(luò)機(jī)制請(qǐng)求取消。它也可以由程序邏輯請(qǐng)求。例如,如果 Web 爬蟲(chóng)檢測(cè)到磁盤(pán)已滿(mǎn),則可能會(huì)自動(dòng)關(guān)閉自身,或者并行算法可能會(huì)啟動(dòng)多個(gè)線程來(lái)搜索解決方案空間的不同區(qū)域,并在其中一個(gè)找到解決方案后取消它們。

僅僅因?yàn)橐粋€(gè)任務(wù)是取消并不意味著它需要一個(gè)中斷請(qǐng)求響應(yīng)立即。對(duì)于在循環(huán)中執(zhí)行代碼的任務(wù),通常每次循環(huán)迭代僅檢查一次中斷。根據(jù)循環(huán)執(zhí)行的時(shí)間長(zhǎng)短,在任務(wù)代碼通知線程中斷之前可能需要一些時(shí)間(通過(guò)使用 Thread.isInterrupted()或通過(guò)調(diào)用阻塞方法輪詢(xún)中斷狀態(tài))。如果任務(wù)需要更具響應(yīng)性,則可以更頻繁地輪詢(xún)中斷狀態(tài)。阻止方法通常在進(jìn)入時(shí)立即輪詢(xún)中斷狀態(tài),InterruptedException 如果設(shè)置為提高響應(yīng)性則拋出 。

吞下一個(gè)中斷是可以接受的,當(dāng)你知道線程即將退出時(shí)。這種情況只發(fā)生在調(diào)用可中斷方法的類(lèi)是一個(gè) Thread,而不是 Runnable 一般或通用庫(kù)代碼的一部分時(shí),如清單5所示。它創(chuàng)建一個(gè)枚舉素?cái)?shù)的線程,直到它被中斷并允許線程退出中斷。尋求主要的循環(huán)在兩個(gè)地方檢查中斷:一次是通過(guò)輪詢(xún) isInterrupted() while 循環(huán)的頭部中的方法,一次是在調(diào)用阻塞 BlockingQueue.put() 方法時(shí)。

清單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。輸入和輸出流類(lèi)可能會(huì)阻止等待 I/O 完成,但它們不會(huì)拋出InterruptedException,并且如果它們被中斷,它們不會(huì)提前返回。但是,在套接字 I/O 的情況下,如果一個(gè)線程關(guān)閉了套接字,那么阻塞其他線程中該套接字上的 I/O 操作將在早期完成SocketException。非阻塞 I/O 類(lèi) java.nio 也不支持可中斷 I/O,但可以通過(guò)關(guān)閉通道或請(qǐng)求喚醒來(lái)類(lèi)似地取消阻塞操作 Selector。同樣,嘗試獲取內(nèi)在鎖(輸入一個(gè) synchronized 塊)不能被中斷,但 ReentrantLock 支持可中斷的采集模式。

不可取消的任務(wù)

有些任務(wù)只是拒絕被打斷,使它們無(wú)法取消。但是,即使是不可取消的任務(wù)也應(yīng)該嘗試保留中斷狀態(tài),以但在調(diào)用堆棧上層的代碼在非可取消任務(wù)完成后想要對(duì)發(fā)生的中斷進(jìn)行響應(yīng)。清單6顯示了一個(gè)等待阻塞隊(duì)列直到某個(gè)項(xiàng)可用的方法,無(wú)論它是否被中斷。為了成為一個(gè)好公民,它在完成后恢復(fù)最終塊中的中斷狀態(tài),以免剝奪呼叫者的中斷請(qǐng)求。它無(wú)法提前恢復(fù)中斷狀態(tài),因?yàn)樗鼤?huì)導(dǎo)致無(wú)限循環(huán) - BlockingQueue.take(), 完成后則可以在進(jìn)入時(shí)立即輪詢(xún)中斷狀態(tài), 如果發(fā)現(xiàn)中斷狀態(tài)設(shè)置,則可以?huà)伋鯥nterruptedException。

清單6. 在返回之前恢復(fù)中斷狀態(tài)的非可執(zhí)行任務(wù)

public Task getNextTask(BlockingQueue<Task> queue) {
  boolean interrupted = false;
  try {
    while (true) {
      try {
        return queue.take();
      } catch (InterruptedException e) {
        interrupted = true;
        // 失敗了再試
      }
    }
  } finally {
    if (interrupted)
      Thread.currentThread().interrupt();
  }
}

摘要

你可以使用 Java 平臺(tái)提供的協(xié)作中斷機(jī)制來(lái)構(gòu)建靈活的取消策略。作業(yè)可以決定它們是否可以取消,它們希望如何響應(yīng)中斷,如果立即返回會(huì)影響應(yīng)用程序的完整性,它們可以推遲中斷以執(zhí)行特定于任務(wù)的清理。即使你想完全忽略代碼中斷,也要確保在捕獲 InterruptedException 并且不重新拋出代碼時(shí)恢復(fù)中斷狀態(tài) ,以便調(diào)用它的代碼能夠發(fā)現(xiàn)中斷。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI