- 相關(guān)推薦
從 JDK 源碼角度看 java 并發(fā)線程的中斷
線程的定義給我們提供了并發(fā)執(zhí)行多個任務(wù)的方式,大多數(shù)情況下我們會讓每個任務(wù)都自行執(zhí)行結(jié)束,這樣能保證事務(wù)的一致性,但是有時我們希望在任務(wù)執(zhí)行中取消任務(wù),使線程停止。在java中要讓線程安全、快速、可靠地停下來并不是一件容易的事,java也沒有提供任何可靠的方法終止線程的執(zhí)行。
從 JDK 源碼角度看 java 并發(fā)線程的中斷
線程調(diào)度策略中有搶占式和協(xié)作式兩個概念,與之類似的是中斷機(jī)制也有協(xié)作式和搶占式。
歷史上Java曾經(jīng)使用stop()方法終止線程的運(yùn)行,他們屬于搶占式中斷。但它引來了很多問題,早已被JDK棄用。調(diào)用stop()方法則意味著:
、賹⑨尫旁摼程所持的所有鎖,而且鎖的釋放不可控。
、诩纯虒伋鯰hreadDeath異常,不管程序運(yùn)行到哪里,但它不總是有效,如果存在被終止線程的鎖競爭;
第一點(diǎn)將導(dǎo)致數(shù)據(jù)一致性問題,這個很好理解,一般數(shù)據(jù)加鎖就是為了保護(hù)數(shù)據(jù)的一致性,而線程停止伴隨所持鎖的釋放,很可能導(dǎo)致被保護(hù)的數(shù)據(jù)呈現(xiàn)不一致性,最終導(dǎo)致程序運(yùn)算出現(xiàn)錯誤。
第二點(diǎn)比較模糊,它要說明的問題就是可能存在某種情況stop()方法不能及時終止線程,甚至可能終止不了線程?慈缦麓a會發(fā)生什么情況,看起來線程mt因?yàn)閳?zhí)行了stop()方法將停止,按理來說就算execut方法是一個死循環(huán),只要執(zhí)行了stop()方法線程將結(jié)束,無限循環(huán)也將結(jié)束。其實(shí)不會,因?yàn)槲覀冊趀xecute方法使用了synchronized修飾,同步方法表示在執(zhí)行execute時將對mt對象進(jìn)行加鎖,另外,Thread的stop()方法也是同步的,于是在調(diào)用mt線程的stop()方法前必須獲取mt對象鎖,但mt對象鎖被execute方法占用,且不釋放,于是stop()方法永遠(yuǎn)獲取不了mt對象鎖,最后得到一個結(jié)論,使用stop()方法停止線程不可靠,它未必總能有效終止線程。
public class ThreadStop {
public static void main(String[] args) {
Thread mt= new MyThread();
mt.start(); try {
Thread.currentThread().sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
}
mt.stop();
} static class MyThread extends Thread {
public void run() {
execute();
}
private synchronized void execute() { while(true) {
}
}
}
}
經(jīng)歷了很長時間的發(fā)展,Java最終選擇用一種協(xié)作式的中斷機(jī)制實(shí)現(xiàn)中斷。協(xié)作式中斷的原理很簡單,其核心是先對中斷標(biāo)識進(jìn)行標(biāo)記,某線程設(shè)置某線程的中斷標(biāo)識位,被標(biāo)記了中斷位的線程在適當(dāng)?shù)臅r間節(jié)點(diǎn)會拋出異常,捕獲異常后做相應(yīng)的處理。實(shí)現(xiàn)協(xié)作中斷有三個要點(diǎn)需要考慮:
、偈窃贘ava層面實(shí)現(xiàn)輪詢中斷標(biāo)識還是在JVM中實(shí)現(xiàn);
、谳喸兊念w粒度的控制,一般顆粒度要盡量小周期盡量短以保證響應(yīng)的及時性;
③輪詢的時間節(jié)點(diǎn)的選擇,其實(shí)就是在哪些方法里面輪詢,例如JVM將Thread類的wait()、sleep()、join()等方法都實(shí)現(xiàn)中斷標(biāo)識的輪詢操作。
中斷標(biāo)識放在哪里?中斷是針對線程實(shí)例而言,從Java層面上看,標(biāo)識變量放到線程中肯定再合適不過了,但由于由JVM維護(hù),所以中斷標(biāo)識具體由本地方法維護(hù)。在Java層面僅僅留下幾個API用于操作中斷標(biāo)識,如下,
public class Thread{
public void interrupt() {……}
public Boolean isInterrupted() {……}
public static Booleaninterrupted() {……}
}
上面三個方法依次用于設(shè)置線程為中斷狀態(tài)、判斷線程狀態(tài)是否中斷、清除當(dāng)前線程中斷狀態(tài)并返回它之前的值。通過interrupt()方法設(shè)置中斷標(biāo)識,假如在非阻塞線程則僅僅只是改變了中斷狀態(tài),線程將繼續(xù)往下運(yùn)行,但假如在可取消阻塞線程中,如正在執(zhí)行sleep()、wait()、join()等方法的線程則會因?yàn)楸辉O(shè)置了中斷狀態(tài)而拋出InterruptedException異常,程序?qū)Υ水惓2东@處理。
上面提到的三個要點(diǎn):
第一是輪詢在哪個層面實(shí)現(xiàn),這個沒有特別的要求,在實(shí)際中只要不出現(xiàn)邏輯問題,在Java層面或JVM層面實(shí)現(xiàn)都是可以的,例如常用的線程睡眠、等待等操作是通過JVM實(shí)現(xiàn),而java并發(fā)框架工具里面的中斷則放到Java實(shí)現(xiàn),不管在哪個層面上去實(shí)現(xiàn),在輪詢過程中都一定要能保證不會產(chǎn)生阻塞。
第二是要保證輪詢的顆粒度盡可能的小周期盡可能短,這關(guān)系到中斷響應(yīng)的`速度。
第三點(diǎn)是關(guān)于輪詢的時間節(jié)點(diǎn)的選取。
針對三要點(diǎn)來看看java并發(fā)框架中是如何支持中斷的,主要在等待獲取鎖的過程中提供中斷操作,下面是偽代碼。只需增加加紅加粗部分邏輯即可實(shí)現(xiàn)中斷支持,在循環(huán)體中每次循環(huán)都對當(dāng)前線程中斷標(biāo)識位進(jìn)行判斷,一旦檢查到線程被標(biāo)記為中斷則拋出InterruptedException異常,高層代碼對此異常捕獲處理即完成中斷處理。總結(jié)起來就是java并發(fā)工具獲取鎖的中斷機(jī)制是在Java層面實(shí)現(xiàn)的,輪詢時間節(jié)點(diǎn)選擇在不斷做嘗試獲取鎖操作過程中,每個循環(huán)的顆粒度比較小,響應(yīng)速度得以保證,且循環(huán)過程不存在阻塞風(fēng)險(xiǎn),保證中斷檢測不會失效。
if(嘗試獲取鎖失敗) {
創(chuàng)建node
使用CAS方式把node插入到隊(duì)列尾部 while(true){ if(嘗試獲取鎖成功并且 node的前驅(qū)節(jié)點(diǎn)為頭節(jié)點(diǎn)){
把當(dāng)前節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)
跳出循環(huán)
}else{
使用CAS方式修改node前驅(qū)節(jié)點(diǎn)的waitStatus標(biāo)識為signal if(修改成功){
掛起當(dāng)前線程 if(當(dāng)前線程中斷位標(biāo)識為true)
拋出InterruptedException異常
}
}
}
}
判斷線程是否處于中斷狀態(tài)其實(shí)很簡單,只需使用Thread.interrupted()操作,如果為true則說明線程處于中斷位,并清除中斷位。至此java并發(fā)工具實(shí)現(xiàn)了支持中斷的獲取鎖操作。
本文從java發(fā)展過程分析了搶占式中斷及協(xié)作式中斷,由于搶占式存在一些缺陷現(xiàn)在已不推薦使用,而協(xié)作式中斷作為推薦做法,盡管在響應(yīng)時間較長,但其具有無可比擬的優(yōu)勢。
協(xié)作式中斷我們可以在JVM層面實(shí)現(xiàn),同樣也可以在Java層面實(shí)現(xiàn),例如JDK并發(fā)工具的中斷即是在Java層面實(shí)現(xiàn),不過如果繼續(xù)深究是因?yàn)镴ava留了幾個API供我們操作線程的中斷標(biāo)識位,這才使Java層面實(shí)現(xiàn)中斷操作得以實(shí)現(xiàn)。
對于java的協(xié)作式中斷機(jī)制有人肯定有人批評,批評者說java沒有搶占式中斷機(jī)制,且協(xié)作式中斷機(jī)制迫使開發(fā)者必須維護(hù)中斷狀態(tài),迫使開發(fā)者必須處理InterruptedException。但肯定者則認(rèn)為,雖然協(xié)作式中斷機(jī)制推遲了中斷請求的處理,但它為開發(fā)人員提供更靈活的中斷處理策略,響應(yīng)性可能不及搶占式,但程序健壯性更強(qiáng)。
【從 JDK 源碼角度看 java 并發(fā)線程的中斷】相關(guān)文章:
java語言源碼解析05-27
java的多線程09-09
java多線程08-31
java多線程介紹08-23
什么是java主線程08-13
java語言的多線程08-29
java線程的幾種狀態(tài)10-22
java多線程教程11-03