淺析Java線程的中斷機(jī)制
線程中斷機(jī)制提供了一種方法,用于將線程從阻塞等待中喚醒,嘗試打斷目標(biāo)線程的現(xiàn)有處理流程,使之響應(yīng)新的命令。Java 留給開發(fā)者這一自由,我們應(yīng)當(dāng)予以善用。
今天我們聊聊 Java 線程的中斷機(jī)制。
線程中斷機(jī)制提供了一種方法,有兩種常見用途:
將線程從阻塞等待中喚醒,并作出相應(yīng)的“受控中斷”處理。
嘗試告知目標(biāo)線程:請(qǐng)打斷現(xiàn)有處理流程,響應(yīng)新的命令。
以第一種用途為例,請(qǐng)看以下代碼:
synchronized (lock) {
try {
while (!check()) {
lock.wait(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
這段代碼使用了 Java 提供的 wait/notify 機(jī)制,線程執(zhí)行 lock.wait() 會(huì)阻塞,有三種情況使線程恢復(fù)運(yùn)行。
1、超時(shí) 1000ms 結(jié)束,正常執(zhí)行下一句代碼。
2、另一個(gè)線程執(zhí)行下述代碼主動(dòng)喚醒
synchronized (lock) {
lock.notifyAll(); // or lock.notify();
}
這也會(huì)正常執(zhí)行下一句代碼。
3、另一個(gè)線程要求等待的線程“中斷”
// 拿到等待中的線程的引用 Thread a; a.interrupt();
被“中斷”的線程 a,會(huì)在 lock.wait() 處拋出 InterruptedException 異常。
綜上所述,你可以認(rèn)為 object.wait() 內(nèi)部在做這些事:
boolean checkTimeout = timeout > 0;
Thread current = Thread.currentThread();
lock.addWaiter(current);
while (!current.isNotified()) {
if (current.isInterrupted()) {
current.clearInterrupted();
throw new InterruptedException();
}
if (checkTimeout) {
if (timeout == 0) break;
timeout--;
}
}
這不完全準(zhǔn)確,因?yàn)?wait 不使用這種“忙輪詢”的方式做檢查,但關(guān)于標(biāo)志位的判斷邏輯是正確的。
讓我們從上文所述的“手動(dòng)發(fā)出中斷”這一操作開始探究
// sun.nio.ch.Interruptible
public interface Interruptible {
void interrupt(Thread var1);
}
// java.lang.Thread
private volatile Interruptible blocker;
private final Object blockerLock = new Object();
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
// Just to set the interrupt flag
private native void interrupt0();
能夠看出,thread.interrupt() 先判斷權(quán)限,然后實(shí)際調(diào)用 interrupt0() 設(shè)置線程的中斷標(biāo)志,如果當(dāng)前線程有 nio 的 Interruptible 那么還會(huì)回調(diào)它。
注意,interrupt0() 只是設(shè)置了線程的中斷標(biāo)志。
當(dāng)一個(gè)線程并不阻塞,沒有在 object.wait(), thread.join(), Thread.sleep() 等不受 Java 程序邏輯控制的區(qū)域時(shí),那么會(huì)發(fā)生什么事情?答案是不會(huì)發(fā)生任何事情,線程是否被打斷只能通過主動(dòng)地檢查中斷標(biāo)志得知。
怎么檢查?Thread 暴露了兩個(gè)接口,Thread.interrupted() 和 thread.isInterrupted()。
// java.lang.Thread
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean clearInterrupted);
能夠看出,兩者都是依靠?jī)?nèi)部的 isInterrupted(boolean),而它會(huì)返回線程是否被打斷,并根據(jù)需要清空中斷標(biāo)志。
當(dāng)一個(gè)函數(shù)調(diào)用會(huì)發(fā)生阻塞,Java 庫(kù)函數(shù)在阻塞的源頭簽名里標(biāo)記 throws InterruptedException,并要求編寫 try catch 處理中斷。
當(dāng)線程發(fā)生了阻塞,就像上文所述,Java 檢查到中斷標(biāo)志,先將其清除,然后拋出 InterruptedException。
// java.lang.Object
public final void wait() throws InterruptedException {
wait(0);
}
public final native void wait(long timeout) throws InterruptedException;
如果一個(gè)線程收到 InterruptedException,之后仍然執(zhí)行了會(huì)引發(fā)阻塞的代碼,它將像“沒事人”一樣繼續(xù)阻塞住。因?yàn)?Java 在內(nèi)部將中斷標(biāo)志清除了!
我們常見地編寫以下三類處理 InterruptedException 的代碼:
將 InterruptedException 交由上層處理。
public void foo() throws InterruptedException {
synchronized (lock) {
lock.wait();
}
}
遇到 InterruptedException 重設(shè)中斷標(biāo)志位。
try {
synchronized (lock) {
lock.wait();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
//break;
}
先忙完,再重新拋出 InterruptedException。
public void bar() throws InterruptedException {
InterruptedException ie = null;
boolean done = false;
while (!done) {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
ie = e;
continue;
}
}
done = true;
}
if (ie != null) {
throw ie;
}
}
如果一個(gè)線程無視中斷標(biāo)志和 InterruptedException,它仍然能夠跑的很好。但這與我們?cè)O(shè)計(jì)多線程的初衷是違背的,我們希望線程之間是和諧的有序協(xié)作以實(shí)現(xiàn)特定功能,因此受控線程應(yīng)當(dāng)對(duì)中斷作出響應(yīng)。而 Java 留給開發(fā)者這一自由,我們應(yīng)當(dāng)予以善用。
以上就是這次給大家介紹的Java線程的中斷機(jī)制相關(guān)知識(shí)的全部?jī)?nèi)容,如果還有任何不明白的可以在下方的留言區(qū)域討論,感謝對(duì)腳本之家的支持。
相關(guān)文章
實(shí)例解決Java異常之OutOfMemoryError的問題
在本篇文章中,我們給大家分享了關(guān)于解決Java異常之OutOfMemoryError的問題的方法,有此需要的朋友們學(xué)習(xí)下。2019-02-02
利用spring的攔截器自定義緩存的實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了利用spring的攔截器自定義緩存的實(shí)現(xiàn)實(shí)例代碼,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02
java通過Arrays.sort(int[] a)實(shí)現(xiàn)由大到小排序的方法實(shí)現(xiàn)
Java中的Arrays.sort()方法是一種內(nèi)置的排序方法,用于對(duì)數(shù)組進(jìn)行排序,本文就來介紹一下java中的Arrays.sort()排序方法的用法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
java并發(fā)編程專題(十)----(JUC原子類)基本類型詳解
這篇文章主要介紹了java JUC原子類基本類型詳解的相關(guān)資料,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07
基于@Valid和@Validated驗(yàn)證List集合的踩坑記錄
這篇文章主要介紹了基于@Valid和@Validated驗(yàn)證List集合的踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
解讀StringBuilder為何比String節(jié)省效率
StringBuilder比String節(jié)省效率的原因主要在于其可變性和性能開銷的降低,StringBuilder在內(nèi)部維護(hù)一個(gè)字符數(shù)組,可以直接在原有基礎(chǔ)上修改,避免了每次拼接時(shí)的額外復(fù)制操作2024-12-12

