深入Java線程中斷的本質(zhì)與編程原則的概述
更新時(shí)間:2013年05月04日 14:51:53 作者:
本篇文章對(duì)Java線程中斷的本質(zhì)與編程原則進(jìn)行了詳細(xì)的概述,需要的朋友參考下
在歷史上,Java試圖提供過(guò)搶占式限制中斷,但問(wèn)題多多,例如前文介紹的已被廢棄的Thread.stop、Thread.suspend和 Thread.resume等。另一方面,出于Java應(yīng)用代碼的健壯性的考慮,降低了編程門檻,減少不清楚底層機(jī)制的程序員無(wú)意破壞系統(tǒng)的概率。
如今,Java的線程調(diào)度不提供搶占式中斷,而采用協(xié)作式的中斷。其實(shí),協(xié)作式的中斷,原理很簡(jiǎn)單,就是輪詢某個(gè)表示中斷的標(biāo)記,我們?cè)谌魏纹胀ùa的中都可以實(shí)現(xiàn)。
例如下面的代碼:
volatile bool isInterrupted;
//…
while(!isInterrupted) {
compute();
}
但是,上述的代碼問(wèn)題也很明顯。當(dāng)compute執(zhí)行時(shí)間比較長(zhǎng)時(shí),中斷無(wú)法及時(shí)被響應(yīng)。另一方面,利用輪詢檢查標(biāo)志變量的方式,想要中斷wait和sleep等線程阻塞操作也束手無(wú)策。
如果仍然利用上面的思路,要想讓中斷及時(shí)被響應(yīng),必須在虛擬機(jī)底層進(jìn)行線程調(diào)度的對(duì)標(biāo)記變量進(jìn)行檢查。是的,JVM中確實(shí)是這樣做的。
下面摘自java.lang.Thread的源代碼:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
//…
private native boolean isInterrupted(boolean ClearInterrupted);
可以發(fā)現(xiàn),isInterrupted被聲明為native方法,取決于JVM底層的實(shí)現(xiàn)。
實(shí)際上,JVM內(nèi)部確實(shí)為每個(gè)線程維護(hù)了一個(gè)中斷標(biāo)記。但應(yīng)用程序不能直接訪問(wèn)這個(gè)中斷變量,必須通過(guò)下面幾個(gè)方法進(jìn)行操作:
public class Thread {
//設(shè)置中斷標(biāo)記
public void interrupt() { ... }
//獲取中斷標(biāo)記的值
public boolean isInterrupted() { ... }
//清除中斷標(biāo)記,并返回上一次中斷標(biāo)記的值
public static boolean interrupted() { ... }
}
通常情況下,調(diào)用線程的interrupt方法,并不能立即引發(fā)中斷,只是設(shè)置了JVM內(nèi)部的中斷標(biāo)記。因此,通過(guò)檢查中斷標(biāo)記,應(yīng)用程序可以做一些特殊操作,也可以完全忽略中斷。
你可能想,如果JVM只提供了這種簡(jiǎn)陋的中斷機(jī)制,那和應(yīng)用程序自己定義中斷變量并輪詢的方法相比,基本也沒(méi)有什么優(yōu)勢(shì)。
JVM內(nèi)部中斷變量的主要優(yōu)勢(shì),就是對(duì)于某些情況,提供了模擬自動(dòng)“中斷陷入”的機(jī)制。
在執(zhí)行涉及線程調(diào)度的阻塞調(diào)用時(shí)(例如wait、sleep和join),如果發(fā)生中斷,被阻塞線程會(huì)“盡可能快的”拋出InterruptedException。因此,我們就可以用下面的代碼框架來(lái)處理線程阻塞中斷:
try {
//wait、sleep或join
}
catch(InterruptedException e) {
//某些中斷處理工作
}
所謂“盡可能快”,我猜測(cè)JVM就是在線程調(diào)度調(diào)度的間隙檢查中斷變量,速度取決于JVM的實(shí)現(xiàn)和硬件的性能。
然而,對(duì)于某些線程阻塞操作,JVM并不會(huì)自動(dòng)拋出InterruptedException異常。例如,某些I/O操作和內(nèi)部鎖操作。對(duì)于這類操作,可以用其他方式模擬中斷:
1)java.io中的異步socket I/O
讀寫(xiě)socket的時(shí)候,InputStream和OutputStream的read和write方法會(huì)阻塞等待,但不會(huì)響應(yīng)java中斷。不過(guò),調(diào)用Socket的close方法后,被阻塞線程會(huì)拋出SocketException異常。
2)利用Selector實(shí)現(xiàn)的異步I/O
如果線程被阻塞于Selector.select(在java.nio.channels中),調(diào)用wakeup方法會(huì)引起ClosedSelectorException異常。
3)鎖獲取
如果線程在等待獲取一個(gè)內(nèi)部鎖,我們將無(wú)法中斷它。但是,利用Lock類的lockInterruptibly方法,我們可以在等待鎖的同時(shí),提供中斷能力。
另外,在任務(wù)與線程分離的框架中,任務(wù)通常并不知道自身會(huì)被哪個(gè)線程調(diào)用,也就不知道調(diào)用線程處理中斷的策略。所以,在任務(wù)設(shè)置了線程中斷標(biāo)記后,并不能確保任務(wù)會(huì)被取消。因此,有以下兩條編程原則:
1)除非你知道線程的中斷策略,否則不應(yīng)該中斷它。
這條原則告訴我們,不應(yīng)該直接調(diào)用Executer之類框架中線程的interrupt方法,應(yīng)該利用諸如Future.cancel的方法來(lái)取消任務(wù)。
2)任務(wù)代碼不該猜測(cè)中斷對(duì)執(zhí)行線程的含義。
這條原則告訴我們,一般代碼遇在到InterruptedException異常時(shí),不應(yīng)該將其捕獲后“吞掉”,而應(yīng)該繼續(xù)向上層代碼拋出。
總之,Java中的非搶占式中斷機(jī)制,要求我們必須改變傳統(tǒng)的搶占式中斷思路,在理解其本質(zhì)的基礎(chǔ)上,采用相應(yīng)的原則和模式來(lái)編程。
如今,Java的線程調(diào)度不提供搶占式中斷,而采用協(xié)作式的中斷。其實(shí),協(xié)作式的中斷,原理很簡(jiǎn)單,就是輪詢某個(gè)表示中斷的標(biāo)記,我們?cè)谌魏纹胀ùa的中都可以實(shí)現(xiàn)。
例如下面的代碼:
volatile bool isInterrupted;
//…
while(!isInterrupted) {
compute();
}
但是,上述的代碼問(wèn)題也很明顯。當(dāng)compute執(zhí)行時(shí)間比較長(zhǎng)時(shí),中斷無(wú)法及時(shí)被響應(yīng)。另一方面,利用輪詢檢查標(biāo)志變量的方式,想要中斷wait和sleep等線程阻塞操作也束手無(wú)策。
如果仍然利用上面的思路,要想讓中斷及時(shí)被響應(yīng),必須在虛擬機(jī)底層進(jìn)行線程調(diào)度的對(duì)標(biāo)記變量進(jìn)行檢查。是的,JVM中確實(shí)是這樣做的。
下面摘自java.lang.Thread的源代碼:
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
//…
private native boolean isInterrupted(boolean ClearInterrupted);
可以發(fā)現(xiàn),isInterrupted被聲明為native方法,取決于JVM底層的實(shí)現(xiàn)。
實(shí)際上,JVM內(nèi)部確實(shí)為每個(gè)線程維護(hù)了一個(gè)中斷標(biāo)記。但應(yīng)用程序不能直接訪問(wèn)這個(gè)中斷變量,必須通過(guò)下面幾個(gè)方法進(jìn)行操作:
public class Thread {
//設(shè)置中斷標(biāo)記
public void interrupt() { ... }
//獲取中斷標(biāo)記的值
public boolean isInterrupted() { ... }
//清除中斷標(biāo)記,并返回上一次中斷標(biāo)記的值
public static boolean interrupted() { ... }
}
通常情況下,調(diào)用線程的interrupt方法,并不能立即引發(fā)中斷,只是設(shè)置了JVM內(nèi)部的中斷標(biāo)記。因此,通過(guò)檢查中斷標(biāo)記,應(yīng)用程序可以做一些特殊操作,也可以完全忽略中斷。
你可能想,如果JVM只提供了這種簡(jiǎn)陋的中斷機(jī)制,那和應(yīng)用程序自己定義中斷變量并輪詢的方法相比,基本也沒(méi)有什么優(yōu)勢(shì)。
JVM內(nèi)部中斷變量的主要優(yōu)勢(shì),就是對(duì)于某些情況,提供了模擬自動(dòng)“中斷陷入”的機(jī)制。
在執(zhí)行涉及線程調(diào)度的阻塞調(diào)用時(shí)(例如wait、sleep和join),如果發(fā)生中斷,被阻塞線程會(huì)“盡可能快的”拋出InterruptedException。因此,我們就可以用下面的代碼框架來(lái)處理線程阻塞中斷:
try {
//wait、sleep或join
}
catch(InterruptedException e) {
//某些中斷處理工作
}
所謂“盡可能快”,我猜測(cè)JVM就是在線程調(diào)度調(diào)度的間隙檢查中斷變量,速度取決于JVM的實(shí)現(xiàn)和硬件的性能。
然而,對(duì)于某些線程阻塞操作,JVM并不會(huì)自動(dòng)拋出InterruptedException異常。例如,某些I/O操作和內(nèi)部鎖操作。對(duì)于這類操作,可以用其他方式模擬中斷:
1)java.io中的異步socket I/O
讀寫(xiě)socket的時(shí)候,InputStream和OutputStream的read和write方法會(huì)阻塞等待,但不會(huì)響應(yīng)java中斷。不過(guò),調(diào)用Socket的close方法后,被阻塞線程會(huì)拋出SocketException異常。
2)利用Selector實(shí)現(xiàn)的異步I/O
如果線程被阻塞于Selector.select(在java.nio.channels中),調(diào)用wakeup方法會(huì)引起ClosedSelectorException異常。
3)鎖獲取
如果線程在等待獲取一個(gè)內(nèi)部鎖,我們將無(wú)法中斷它。但是,利用Lock類的lockInterruptibly方法,我們可以在等待鎖的同時(shí),提供中斷能力。
另外,在任務(wù)與線程分離的框架中,任務(wù)通常并不知道自身會(huì)被哪個(gè)線程調(diào)用,也就不知道調(diào)用線程處理中斷的策略。所以,在任務(wù)設(shè)置了線程中斷標(biāo)記后,并不能確保任務(wù)會(huì)被取消。因此,有以下兩條編程原則:
1)除非你知道線程的中斷策略,否則不應(yīng)該中斷它。
這條原則告訴我們,不應(yīng)該直接調(diào)用Executer之類框架中線程的interrupt方法,應(yīng)該利用諸如Future.cancel的方法來(lái)取消任務(wù)。
2)任務(wù)代碼不該猜測(cè)中斷對(duì)執(zhí)行線程的含義。
這條原則告訴我們,一般代碼遇在到InterruptedException異常時(shí),不應(yīng)該將其捕獲后“吞掉”,而應(yīng)該繼續(xù)向上層代碼拋出。
總之,Java中的非搶占式中斷機(jī)制,要求我們必須改變傳統(tǒng)的搶占式中斷思路,在理解其本質(zhì)的基礎(chǔ)上,采用相應(yīng)的原則和模式來(lái)編程。
相關(guān)文章
mac系統(tǒng)剛安裝的idea打不開(kāi)的問(wèn)題及解決
這篇文章主要介紹了mac系統(tǒng)剛安裝的idea打不開(kāi)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09java?Semaphore共享鎖實(shí)現(xiàn)原理解析
這篇文章主要為大家介紹了Semaphore共享鎖實(shí)現(xiàn)原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01自定義@RequestBody注解如何獲取JSON數(shù)據(jù)
這篇文章主要介紹了自定義@RequestBody注解如何獲取JSON數(shù)據(jù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04SpringBoot詳解如何實(shí)現(xiàn)讀寫(xiě)分離
當(dāng)響應(yīng)的瓶頸在數(shù)據(jù)庫(kù)的時(shí)候,就要考慮數(shù)據(jù)庫(kù)的讀寫(xiě)分離,當(dāng)然還可以分庫(kù)分表,那是單表數(shù)據(jù)量特別大,當(dāng)單表數(shù)據(jù)量不是特別大,但是請(qǐng)求量比較大的時(shí)候,就要考慮讀寫(xiě)分離了.具體的話,還是要看自己的業(yè)務(wù)...如果還是很慢,那就要分庫(kù)分表了...我們這篇就簡(jiǎn)單講一下讀寫(xiě)分離2022-05-05Java11中基于嵌套關(guān)系的訪問(wèn)控制優(yōu)化詳解
Java(和其他語(yǔ)言)通過(guò)內(nèi)部類支持嵌套類,要使其正常工作,需要編譯器執(zhí)行一些技巧,下面這篇文章主要給大家介紹了關(guān)于Java11中基于嵌套關(guān)系的訪問(wèn)控制優(yōu)化的相關(guān)資料,需要的朋友可以參考下2022-01-01SpringBoot使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證
本文主要介紹了SpringBoot使用Sa-Token實(shí)現(xiàn)登錄認(rèn)證,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04淺談Java高并發(fā)解決方案以及高負(fù)載優(yōu)化方法
這篇文章主要介紹了淺談Java高并發(fā)解決方案以及高負(fù)載優(yōu)化方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Java的關(guān)鍵字與標(biāo)識(shí)符小結(jié)
這篇文章主要介紹了Java的關(guān)鍵字與標(biāo)識(shí)符,總結(jié)整理了Java各種常見(jiàn)的關(guān)鍵字與標(biāo)識(shí)符功能、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04