Java 并發(fā)之線程中斷

一玄呛、前言

所謂線程中斷诬留,其實(shí)就是終止一個(gè)線程。在使用 Java 線程時(shí)搔弄,除了線程自行正常結(jié)束幅虑,很多時(shí)候也需要提前結(jié)束一個(gè)線程的執(zhí)行過(guò)程。Thread 類(lèi)中有一個(gè)與 start() 相對(duì)應(yīng)的 stop() 方法顾犹,可以從外部結(jié)束一個(gè)線程的執(zhí)行倒庵。但是這個(gè)方法是極不推薦使用的,因?yàn)閺耐獠繌?qiáng)行結(jié)束一個(gè)線程的執(zhí)行炫刷,會(huì)導(dǎo)致不可預(yù)知的錯(cuò)誤擎宝,因?yàn)檫@樣往往會(huì)在錯(cuò)誤的時(shí)間結(jié)束一個(gè)線程的執(zhí)行。

所以浑玛,在 Java 線程機(jī)制中绍申,就有了另一種結(jié)束線程的方式,那就是中斷。中斷极阅,簡(jiǎn)而言之就是讓線程外部可以設(shè)置一個(gè)標(biāo)記值胃碾,而線程內(nèi)部在執(zhí)行時(shí)則檢查這個(gè)值,來(lái)獲知此線程是否應(yīng)該結(jié)束了涂屁。

二书在、可以用來(lái)設(shè)置中斷的方法

除了 Thread.interrupt() 方法以外,下列 JDK 中的方法也會(huì)設(shè)置中斷(也是通過(guò)調(diào)用 Thread.interrupt() 來(lái)實(shí)現(xiàn)的):

  • FutureTask.cancel()
  • ExecutorService.shutdownNow() 這個(gè)方法會(huì)調(diào)用線程池中所有線程的中斷方法拆又,不論它們是空閑的還是運(yùn)行中的儒旬。而 ExecutorService.shutdown() 方法只能中斷空閑的線程。

上面只是舉兩個(gè) JDK 中應(yīng)用到了線程中斷的例子帖族,這樣的例子還有很多栈源,就不一一列舉了。當(dāng)然竖般,為了能響應(yīng)中斷甚垦,在你所寫(xiě)的 Runnable 或 Callable 代碼中,必須通過(guò) Thread.isInterrupted()涣雕、Thread.interrupted() 方法艰亮,或者捕獲 InterruptedException 等的中斷異常來(lái)發(fā)現(xiàn)線程中斷并處理,否則線程是不會(huì)自行提前結(jié)束的挣郭。

三迄埃、能被中斷的方法

在 JDK 和其它類(lèi)庫(kù)和框架中,能相應(yīng)中斷的方法是很多的兑障。下面列出幾個(gè)常見(jiàn)的 JDK 中能響應(yīng)中斷的方法:

  • Thread.sleep()
  • Object.wait()
  • BlockingQueue.put(), BlockingQueue.take()
  • ReentrantLock.lockInterruptibly(), Condition.await()
  • ServerSocketChannel.accept(), SocketChannel.open()

等等

JDK 中能響應(yīng)中斷的方法基本上都是拋出異常侄非。這些方法基本可被分為兩類(lèi):一類(lèi)是并發(fā)相關(guān)的,一類(lèi)是 IO 相關(guān)的流译。

四逞怨、中斷的處理

1. 處理 InterruptedException(也包括其它中斷異常)

InterruptedException 是最常見(jiàn)的中斷表現(xiàn)形式。所以如何處理 InterruptedException 便成為 Java 中斷知識(shí)中的必修課福澡。在這方面 IBM developerWorks 上有篇文章講的很好叠赦,我在下面的參考文章中會(huì)列出鏈接。我這里就對(duì)這篇文章做一個(gè)總結(jié)革砸,各位看客可以去讀那邊文章以獲得細(xì)節(jié)知識(shí)眯搭。

處理 InterruptedException 可有以下幾種方式(下面使用的代碼均引用自 Java 理論與實(shí)踐: 處理 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();
    }
}

在 catch 中做處理后在拋出

因?yàn)?InterruptedException 的拋出业岁,會(huì)打斷方法執(zhí)行鳞仙,使正在進(jìn)行的工作只完成一部分。在有些情況下笔时,你就需要進(jìn)行諸如回滾的處理棍好。所以在這種情況便需要在 catch 塊中進(jìn)行處理之后在向上拋出 InterruptedException

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 時(shí)要恢復(fù)中斷狀態(tài)

很多時(shí)候,由于你所實(shí)現(xiàn)的接口定義的限制借笙,你很可能無(wú)法拋出 InterruptedException扒怖。例如實(shí)現(xiàn) Runnable 接口以編寫(xiě)業(yè)務(wù)代碼。這時(shí)业稼,你就無(wú)法再向上拋出 InterruptedException 了盗痒。此時(shí)你應(yīng)該使用 Thread.currentThread().interrupt() 方法去恢復(fù)中斷狀態(tài)。因?yàn)樽枞椒ㄔ趻伋?InterruptedException 時(shí)會(huì)清除當(dāng)前線程的中斷狀態(tài)低散,如果此時(shí)不恢復(fù)中斷狀態(tài)俯邓,也不拋出 InterruptedException,那中斷信息便會(huì)丟失熔号,上層調(diào)用者也就無(wú)法得知中斷的發(fā)生稽鞭。這樣便有可能導(dǎo)致任務(wù)無(wú)法正確終止的情況方式。

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

在這里引镊,我們要清楚中斷的意義在于并發(fā)或異步場(chǎng)景下任務(wù)的終止朦蕴。所以,如果你的代碼在吞掉 InterruptedException 而不拋出時(shí)并不會(huì)造成任務(wù)無(wú)法被正確終止的情況方式弟头,那也可以不再恢復(fù)中斷吩抓。

還是 Runnable 的例子,大家都知道 Runnable 多數(shù)時(shí)候是提交到線程池來(lái)運(yùn)行赴恨。并且通常是作為任務(wù)的頂層容器來(lái)使用的疹娶,也就是說(shuō)在線程池和 Runnable 實(shí)現(xiàn)之間,沒(méi)有別的調(diào)用層了嘱支。那么在 try-catch InterruptedException 之后,便可不用在恢復(fù)線程中斷了挣饥。

但如果不是上述情況除师,你所寫(xiě)的,帶有 try-catch InterruptedException 的方法會(huì)被其它的扔枫、非線程池類(lèi)的方法調(diào)用汛聚。例如有 A, B 兩個(gè)方法,A 被 B 方法調(diào)用短荐,A 中捕獲 InterruptedException 后沒(méi)有恢復(fù)線程中斷倚舀,而 B 方法中有一個(gè)循環(huán),通過(guò)檢查線程中斷來(lái)決定是否退出忍宋,或者 B 方法在調(diào)用 A 方法之后痕貌,還有個(gè)阻塞的方法。如果不恢復(fù)線程中斷糠排,那便會(huì)造成線程無(wú)法按照期望被終止的情況發(fā)生舵稠。

自己拋出 InterruptedException

有時(shí)候,你需要“無(wú)中生有”地創(chuàng)造出一個(gè) InterruptedException 以表示中斷的發(fā)生。在這個(gè)時(shí)候哺徊,你需要使用 Thread.isInterrupted()Thread.interrupted() 來(lái)檢測(cè)中斷的發(fā)生室琢。那究竟是用這兩者中的哪一個(gè)?其實(shí)看了前面的部分我們知道落追,拋出 InterruptedException 時(shí)盈滴,線程中斷狀態(tài)便被清除。所以轿钠,在你自己實(shí)現(xiàn)類(lèi)似功能的時(shí)候巢钓,也要遵循這一原則,即拋出 InterruptedException 后需要清除當(dāng)前線程的中斷狀態(tài)谣膳。因此竿报,此時(shí)需要使用 Thread.interrupted()

其實(shí)继谚,你要是看 JDK 源代碼烈菌,就會(huì)發(fā)現(xiàn),JDK 中并發(fā)類(lèi)也是這么做的

NOTE: 使用 Thread.interrupt() 和 InterruptedException 中的哪種方法表示中斷花履?上面提到了一種情況是由于接口的限制而無(wú)法拋出 InterruptedException芽世,這時(shí)你別無(wú)選擇,只能用 Thread.interrupt() 恢復(fù)中斷诡壁。除了這種情況济瓢,其它的時(shí)候推薦使用 InterruptedException 來(lái)表示中斷。當(dāng)方法聲明拋出 InterruptedException 時(shí)妹卿,它就是在告訴調(diào)用者旺矾,我這個(gè)方法可能會(huì)花費(fèi)很多的時(shí)間,而你可以通過(guò)線程中斷來(lái)終止調(diào)用夺克。通過(guò) InterruptedException 來(lái)表示中斷箕宙,含義更清晰,反應(yīng)也更迅速铺纽。

2. 無(wú)法被中斷的情況

synchronized

阻塞在 synchronized 的內(nèi)置鎖上是無(wú)法被中斷的柬帕,如果需要可以被中斷的鎖〗泼牛可以使用 Java 5 concurrent 中的 Lock陷寝。

Java IO(不包含 NIO 和 AIO)

對(duì) Java IO 有了解的人都知道,ServerSocket.accept() 是一個(gè)阻塞方法其馏,但是 Thread.interrupt() 對(duì)它毫無(wú)影響凤跑。要想終止 ServerSocket.accept() 的等待,唯一方法就是調(diào)用 close() 方法叛复。

3. 線程中斷與 Java IO

上面說(shuō)到傳統(tǒng)的 Java IO 中的阻塞方法無(wú)法響應(yīng)線程中斷饶火,因?yàn)?Java IO 出現(xiàn)的時(shí)候還沒(méi)有中斷機(jī)制鹏控。在 Java 1.4 引入的 Java NIO 已經(jīng)可以很好地支持 Java 線程中斷了。

如果是使用 XXXChannel肤寝,線程中斷會(huì)導(dǎo)致 accept(), open() 等阻塞方法拋出 ClosedByInterruptException当辐。

五、線程中斷與微服務(wù)的高可用

談到微服務(wù)的高可用就不得不說(shuō)熔斷降級(jí)鲤看,談到熔斷降級(jí)就不得不說(shuō) Hystrix缘揪。而 Hystrix 超時(shí)取消任務(wù)其實(shí)就是使用的線程中斷。當(dāng)任務(wù)執(zhí)行超過(guò) HystrixCommand 中的超時(shí)設(shè)置時(shí)义桂,Hystrix 便會(huì)在中斷執(zhí)行任務(wù)的線程找筝。所以,當(dāng)你使用 Hystrix 時(shí)慷吊,你的代碼一定要能響應(yīng)中斷袖裕。

六、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溉瓶,一起剝皮案震驚了整個(gè)濱河市急鳄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌堰酿,老刑警劉巖疾宏,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異触创,居然都是意外死亡坎藐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)哼绑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岩馍,“玉大人,你說(shuō)我怎么就攤上這事抖韩≈鳎” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵帽蝶,是天一觀的道長(zhǎng)赦肋。 經(jīng)常有香客問(wèn)我块攒,道長(zhǎng)励稳,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任囱井,我火速辦了婚禮驹尼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庞呕。我一直安慰自己新翎,他們只是感情好程帕,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著地啰,像睡著了一般愁拭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亏吝,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天岭埠,我揣著相機(jī)與錄音,去河邊找鬼蔚鸥。 笑死惜论,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的止喷。 我是一名探鬼主播馆类,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼弹谁!你這毒婦竟也來(lái)了乾巧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤僵闯,失蹤者是張志新(化名)和其女友劉穎卧抗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鳖粟,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡社裆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了向图。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泳秀。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖榄攀,靈堂內(nèi)的尸體忽然破棺而出嗜傅,到底是詐尸還是另有隱情,我是刑警寧澤檩赢,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布吕嘀,位于F島的核電站,受9級(jí)特大地震影響贞瞒,放射性物質(zhì)發(fā)生泄漏偶房。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一军浆、第九天 我趴在偏房一處隱蔽的房頂上張望棕洋。 院中可真熱鬧,春花似錦乒融、人聲如沸掰盘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)愧捕。三九已至奢驯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間次绘,已是汗流浹背叨橱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留断盛,地道東北人罗洗。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像钢猛,于是被迫代替她去往敵國(guó)和親伙菜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容