您好,登錄后才能下訂單哦!
處理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ì)億速云的支持。
免責(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)容。