Java中的線(xiàn)程中斷機(jī)制和LockSupport詳解
線(xiàn)程中斷機(jī)制
線(xiàn)程中斷機(jī)制概念
首先,一個(gè)線(xiàn)程不應(yīng)該由其他線(xiàn)程來(lái)強(qiáng)制中斷或停止,而是應(yīng)該由線(xiàn)程自己自行停止,自己來(lái)決定自己的命運(yùn)。所以,Thread.stop, Thread.suspend, Thread.resume 都已經(jīng)被廢棄了。
其次,在Java中沒(méi)有辦法立即停止一條線(xiàn)程,然而停止線(xiàn)程卻顯得尤為重要,如取消一個(gè)耗時(shí)操作。因此,Java提供了一種用于停止線(xiàn)程的協(xié)商機(jī)制―—中斷,也即中斷標(biāo)識(shí)協(xié)商機(jī)制。
中斷只是一種協(xié)作協(xié)商機(jī)制,Java沒(méi)有給中斷增加任何語(yǔ)法,中斷的過(guò)程完全需要程序員自己實(shí)現(xiàn)。 若要中斷一個(gè)線(xiàn)程,你需要手動(dòng)調(diào)用該線(xiàn)程的interrupt方法,該方法也僅僅是將線(xiàn)程對(duì)象的中斷標(biāo)識(shí)設(shè)成true;接著你需要自己寫(xiě)代碼不斷地檢測(cè)當(dāng)前線(xiàn)程的標(biāo)識(shí)位,如果為true,表示別的線(xiàn)程請(qǐng)求這條線(xiàn)程中斷,此時(shí)究竟該做什么需要你自己寫(xiě)代碼實(shí)現(xiàn)。
每個(gè)線(xiàn)程對(duì)象中都有一個(gè)中斷標(biāo)識(shí)位,用于表示線(xiàn)程是否被中斷;該標(biāo)識(shí)位為true表示中斷,為false表示未中斷;通過(guò)調(diào)用線(xiàn)程對(duì)象的interrupt方法將該線(xiàn)程的標(biāo)識(shí)位設(shè)為true;可以在別的線(xiàn)程中調(diào)用,也可以在自己的線(xiàn)程中調(diào)用。
常用API
public void interrupt()
實(shí)例方法,Just to set the interrupt flag 實(shí)例方法interrupt()僅僅是設(shè)置線(xiàn)程的中斷狀態(tài)為true,發(fā)起一個(gè)協(xié)商而不會(huì)立刻停止線(xiàn)程
public static boolean interrupted()
靜態(tài)方法,Thread.interrupted();判斷線(xiàn)程是否被中斷并清除當(dāng)前中斷狀態(tài)。這個(gè)方法做了兩件事: 返回當(dāng)前線(xiàn)程的中斷狀態(tài),測(cè)試當(dāng)前線(xiàn)程是否已被中斷 將當(dāng)前線(xiàn)程的中斷狀態(tài)清零并重新設(shè)為false,清除線(xiàn)程的中斷狀態(tài) 此方法有點(diǎn)不好理解,如果連續(xù)兩次調(diào)用此方法,則第二次調(diào)用將返回false,因?yàn)檫B續(xù)調(diào)用兩次的結(jié)果可能不一樣
中斷標(biāo)識(shí)被清空,如果該方法被連續(xù)調(diào)用兩次,第二次調(diào)用將返回false 除非當(dāng)前線(xiàn)程在第一次和第二次調(diào)用該方法之間再次被interrupt
public boolean isInterrupted()
實(shí)例方法,判斷當(dāng)前線(xiàn)程是否被中斷(通過(guò)檢查中斷標(biāo)志位)
interrupted()與isInterrupted()的區(qū)別

方法的注釋也清晰的表達(dá)了“中斷狀態(tài)將會(huì)根據(jù)傳入的ClearInterrupted參數(shù)值確定是否重置“ 所以,靜態(tài)方法interrupted將會(huì)清除中斷狀態(tài)(傳入的參數(shù)ClearInterrupted為true) , 實(shí)例方法isInterrupted則不會(huì)(傳入的參數(shù)ClearInterrupted為false)。
如何停止中斷運(yùn)行中的線(xiàn)程?
通過(guò)一個(gè)volatile變量實(shí)現(xiàn)
private static volatile boolean isStop = false;
public static void main(String[] args) {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println(Thread.currentThread().getName() + "線(xiàn)程isStop = true,自己退出");
break;
}
System.out.println("-------hello interrupt--------");
}
}, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
isStop = true;
}通過(guò)AtomicBoolean
private static final AtomicBoolean atomicBoolean = new AtomicBoolean(true);
public static void main(String[] args) {
new Thread(() -> {
while (atomicBoolean.get()) {
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------hello------");
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicBoolean.set(false);
}通過(guò)Thread類(lèi)自帶的中斷api實(shí)例方法實(shí)現(xiàn)
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("-----t1 線(xiàn)程被中斷了,程序結(jié)束");
break;
}
System.out.println("-----hello-------");
}
}, "t1");
t1.start();
System.out.println("t1是否被中斷:" + t1.isInterrupted());
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
System.out.println("t1是否被中斷:" + t1.isInterrupted());
}在需要中斷的線(xiàn)程中不斷監(jiān)聽(tīng)中斷狀態(tài),一旦發(fā)生中斷,就執(zhí)行相應(yīng)的中斷處理業(yè)務(wù)邏輯stop線(xiàn)程
具體來(lái)說(shuō),當(dāng)對(duì)一個(gè)線(xiàn)程,調(diào)用interrupt()時(shí):
- 如果線(xiàn)程處于正?;顒?dòng)狀態(tài),那么會(huì)將該線(xiàn)程的中斷標(biāo)志設(shè)置為 true,僅此而已。被設(shè)置中斷標(biāo)志的線(xiàn)程將繼續(xù)正常運(yùn)行,不受影響。所以,interrupt()并不能真正的中斷線(xiàn)程,需要被調(diào)用的線(xiàn)程自己進(jìn)行配合才行。
- 如果線(xiàn)程處于被阻塞狀態(tài)(例如處于sleep, wait, join等狀態(tài)),在別的線(xiàn)程中調(diào)用當(dāng)前線(xiàn)程對(duì)象的interrupt方法,那么線(xiàn)程將立即退出被阻塞狀態(tài),并拋出一個(gè)InterruptedException異常。在catch塊中應(yīng)該加上一行代碼:Thread.currentThread().interrupt(); 為什么要寫(xiě)這個(gè)? 這是維持狀態(tài)。sleep(),wait()方法拋出InterruptException異常后會(huì)清除中斷標(biāo)志,即把中斷標(biāo)志設(shè)為false。而你又捕獲了InterruptException,這時(shí)你基本上阻止任何更高級(jí)別的方法/線(xiàn)程組注意到中斷。這可能會(huì)導(dǎo)致問(wèn)題。通過(guò)調(diào)用Thread.currentThread().interrupt(),你可以設(shè)置線(xiàn)程的中斷標(biāo)志(即把中斷標(biāo)志設(shè)為true),因此更高級(jí)別的中斷處理程序會(huì)注意到它并且可以正確處理它。
當(dāng)前線(xiàn)程的中斷標(biāo)識(shí)為true,是不是線(xiàn)程就立刻停止? 實(shí)例方法interrupt()僅僅是設(shè)置線(xiàn)程的中斷狀態(tài)位為true,不會(huì)停止線(xiàn)程。中斷只是一種協(xié)商機(jī)制,修改中斷標(biāo)識(shí)位僅此而已,不是立刻stop打斷
sleep方法拋出InterruptedException后,中斷標(biāo)識(shí)也被清空置為false,我們?cè)赾atch沒(méi)有通過(guò)調(diào)用th.interrupt()方法再次將中斷標(biāo)識(shí)置為true,這就導(dǎo)致無(wú)限循環(huán)了
LockSupport
LockSupport初探
LockSupport是用來(lái)創(chuàng)建鎖和其他同步類(lèi)的基本線(xiàn)程阻塞原語(yǔ)。
LockSupport中的park()和 unpark()的作用分別是阻塞線(xiàn)程和解除阻塞線(xiàn)程
LockSupport類(lèi)中的park等待和unpark喚醒 LockSupport類(lèi)使用了一種名為Pemit(許可)的概念來(lái)做到阻塞和喚醒線(xiàn)程的功能,每個(gè)線(xiàn)程都有一個(gè)許可(permit), 但與Semaphore不同的是,許可的累加上限是1。 permit許可證默認(rèn)沒(méi)有不能放行,所以一開(kāi)始調(diào)park()方法當(dāng)前線(xiàn)程就會(huì)阻塞,直到別的線(xiàn)程給當(dāng)前線(xiàn)程的發(fā)放permit,park方法才會(huì)被喚醒。 調(diào)用unpark(thread)方法后,就會(huì)將thread線(xiàn)程的許可證permit發(fā)放,會(huì)自動(dòng)喚醒park線(xiàn)程,即之前阻塞中的LockSuppot.pak()方法會(huì)立即返回。
線(xiàn)程等待喚醒機(jī)制
3種讓線(xiàn)程等待和喚醒的方法
- 方式1:使用object中的wait()方法讓線(xiàn)程等待,使用Object中的notify()方法喚醒線(xiàn)程
- 方式2:使用Juc包中Condition的await()方法讓線(xiàn)程等待,使用signal()方法喚醒線(xiàn)程
- 方式3:LockSupport類(lèi)可以阻塞當(dāng)前線(xiàn)程以及喚醒指定被阻塞的線(xiàn)程
上述兩個(gè)對(duì)象object和Condition使用的限制條件 線(xiàn)程先要獲得并持有鎖,必須在鎖塊(synchronized或lock)中 必須要先等待后喚醒,線(xiàn)程才能夠被喚醒
LockSupport優(yōu)勢(shì) 正常+無(wú)鎖塊要求 之前錯(cuò)誤的先喚醒后等待,LockSupport照樣支持
為什么可以突破wait/notify的原有調(diào)用順序? 因?yàn)閡npark獲得了一個(gè)憑證,之后再調(diào)用park方法,就可以名正言順的憑證消費(fèi),故不會(huì)阻塞。先發(fā)放了憑證后續(xù)可以暢通無(wú)阻。
為什么喚醒兩次后阻塞兩次,但最終結(jié)果還會(huì)阻塞線(xiàn)程? 因?yàn)閼{證的數(shù)量最多為1,連續(xù)調(diào)用兩次unpark和調(diào)用一次 unpark效果一樣,只會(huì)增加一個(gè)憑證;而調(diào)用兩次park卻需要消費(fèi)兩個(gè)憑證,杯夠,不能放行
LockSupport總結(jié):
LockSupport是用來(lái)創(chuàng)建鎖和其他同步類(lèi)的基本線(xiàn)程阻塞原語(yǔ)。 LockSupport是一個(gè)線(xiàn)程阻塞工具類(lèi),所有的方法都是靜態(tài)方法,可以讓線(xiàn)程在任意位置阻塞,阻塞之后也有對(duì)應(yīng)的喚醒方法。歸根結(jié)底,LockSupport調(diào)用的Unsafe中的native代碼。
LockSupport提供park()和unpark()方法實(shí)現(xiàn)阻塞線(xiàn)程和解除線(xiàn)程阻塞的過(guò)程LockSupport和每個(gè)使用它的線(xiàn)程都有一個(gè)許可(permit)關(guān)聯(lián)。每個(gè)線(xiàn)程都有一個(gè)相關(guān)的permit, permit最多只有一個(gè),重復(fù)調(diào)用unpark也不會(huì)積累憑證。
線(xiàn)程阻塞需要消耗憑證(permit),這個(gè)憑證最多只有1個(gè)。當(dāng)調(diào)用park方法時(shí)如果有憑證,則會(huì)直接消耗掉這個(gè)憑證然后正常退出;如果無(wú)憑證,就必須阻塞等待憑證可用;而unpark則相反,它會(huì)增加一個(gè)憑證,但憑證最多只能有1個(gè),累加無(wú)效。
到此這篇關(guān)于Java中的線(xiàn)程中斷機(jī)制和LockSupport詳解的文章就介紹到這了,更多相關(guān)Java線(xiàn)程中斷機(jī)制和LockSupport內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java中斷一個(gè)線(xiàn)程操作示例
- 聊聊Java 中的線(xiàn)程中斷
- 詳解Java 線(xiàn)程中斷
- Java并發(fā)編程之線(xiàn)程中斷
- java高并發(fā)的線(xiàn)程中斷的幾種方式詳解
- Java線(xiàn)程中斷interrupt的常用方法
- java中斷線(xiàn)程的正確姿勢(shì)完整示例
- java中線(xiàn)程中斷的實(shí)現(xiàn)示例
- Java中線(xiàn)程中斷的幾種方法小結(jié)
- Java線(xiàn)程中斷及線(xiàn)程中斷的幾種使用場(chǎng)景小結(jié)
- java線(xiàn)程的中斷和同步問(wèn)題的實(shí)現(xiàn)
相關(guān)文章
Springboot整合x(chóng)xl-job實(shí)現(xiàn)動(dòng)態(tài)傳參
XXL-JOB是一個(gè)分布式任務(wù)調(diào)度平臺(tái),本文主要介紹了Springboot整合x(chóng)xl-job實(shí)現(xiàn)動(dòng)態(tài)傳參,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03
Spring Cloud中各組件超時(shí)總結(jié)
在大家學(xué)習(xí)spring cloud的時(shí)候組件是必不可少的一部分,下面這篇文章主要給大家介紹了關(guān)于Spring Cloud中各組件超時(shí)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-11-11

