今天我們聊聊 Java 線程的中斷機制。 線程中斷機制提供了一種方法,用於將線程從阻塞等待中喚醒,並作出相應的“受控中斷”處理。 這段代碼使用了 Java 提供的 wait/notify 機制,線程執行 lock.wait() 會阻塞,有三種情況使線程恢復運行。 超時 1000ms 結束,正常執行下 ...
今天我們聊聊 Java 線程的中斷機制。
線程中斷機制提供了一種方法,用於將線程從阻塞等待中喚醒,並作出相應的“受控中斷”處理。
synchronized (lock) {
try {
while (!check()) {
lock.wait(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
這段代碼使用了 Java 提供的 wait/notify
機制,線程執行 lock.wait()
會阻塞,有三種情況使線程恢復運行。
-
超時 1000ms 結束,正常執行下一句代碼。
-
另一個線程執行下述代碼主動喚醒
synchronized (lock) { lock.notifyAll(); // or lock.notify(); }
這也會正常執行下一句代碼。
-
另一個線程要求等待的線程“中斷”
// 拿到等待中的線程的引用 Thread a; a.interrupt();
被“中斷”的線程 a,會在
lock.wait()
處拋出InterruptedException
異常。
綜上所述,你可以認為 object.wait()
內部在做這些事:
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--;
}
}
這不完全準確,因為 wait 不使用這種“忙輪詢”的方式做檢查,但關於標誌位的判斷邏輯是正確的。
讓我們從手動中斷開始探究,
// 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()
先判斷許可權,然後實際調用 interrupt0()
設置線程的中斷標誌,如果當前線程有 nio 的 Interruptible
那麼還會回調它。
註意,interrupt0() 只是設置了線程的中斷標誌。
一個線程怎麼知道自己被打斷了?
// java.lang.Thread
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean clearInterrupted);
也就是說, isInterrupted(boolean)
會返回線程是否被打斷,並根據需要清空中斷標誌。
我們發現,當一個線程並不阻塞,沒有在 object.wait()
, thread.join()
, Thread.sleep()
等不受 Java 程式邏輯控制的區域時,那麼線程是否被打斷只能通過檢查中斷標誌得知。
當一個函數調用可能阻塞,Java 會在阻塞的源頭簽名里標記 throws InterruptedException
,並要求編寫 try catch
處理中斷。
當線程阻塞,就像上文所述,Java 檢查到中斷標誌,先將其清除,然後拋出 InterruptedException
。
// java.lang.Object
public final void wait() throws InterruptedException {
wait(0);
}
public final native void wait(long timeout) throws InterruptedException;
如果一個線程收到 InterruptedException
,之後仍然執行了會引發阻塞的代碼,它將像“沒事人”一樣繼續阻塞住。因為 Java 在內部將中斷標誌清除了!
我們常見地編寫以下三類處理 InterruptedException
的代碼:
將 InterruptedException
交由上層處理。
public void foo() throws InterruptedException {
synchronized (lock) {
lock.wait();
}
}
遇到 InterruptedException
重設中斷標誌位。
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;
}
}
如果一個線程無視中斷標誌和 InterruptedException
,它仍然能夠跑的很好。但 這與我們設計多線程的初衷是違背的 ,我們希望線程之間是和諧的有序協作以實現特定功能,因此 受控線程應當對中斷作出響應 。而 Java 留給開發者這一自由,我們應當予以善用。如果你想學習java可以加我的學習群669823128