執行緒池生命週期包括:
- RUNNING:接收新的任務並處理佇列中的任務
- SHUTDOWN:不接收新的任務,但是處理佇列中的任務
- STOP:不接收新的任務,不處理佇列中的任務,同時中斷處理中的任務
- TIDYING:所有的任務處理完成,有效的執行緒數是0
- TERMINATED:terminated()方法執行完畢
轉換成TIDYING狀態的執行緒會執行terminated方法。執行完terminated()方法之後,所有等待在awaitTermination()就會返回。
轉換過程為:
執行緒池是空的即有效執行緒數是0
取消
如果程式碼能夠在某個操作正常完全之前置入“完成”狀態,那麼這個操作就稱為可取消的。java中提供了協作式機制,使請求取消的任務和程式碼遵循一種協商好的協議。
執行緒中斷
執行緒中斷就是一種協作機制。它並不會真正的中斷一個正在執行的執行緒,而只是發出中斷請求,然後由執行緒在下一個合適的時刻中斷自己。
Thread中的中斷方法包括
interrupt
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();//非當前執行緒有可能丟擲SecurityException
synchronized (blockerLock) {
//用於執行可終端的IO操作對應的方法
Interruptible b = blocker;
if (b != null) {
//僅設定終端標記
interrupt0();
//執行實現了Interruptible介面的防範
b.interrupt(this);
return;
}
}
//僅設定終端標記
interrupt0();
}
複製程式碼
呼叫它根據執行緒的不同場景,有不同的結果
-
如果執行緒阻塞的是一個可以中斷的channel,那麼channel會被關閉,同時執行緒會收到java.nio.channels.ClosedByInterruptException,並且會設定中斷標誌
//AbstractInterruptibleChannel中: protected final void begin() { if (interruptor == null) { interruptor = new Interruptible() { public void interrupt(Thread target) { synchronized (closeLock) { if (!open) return; open = false; interrupted = target; try { //關閉channel AbstractInterruptibleChannel.this.implCloseChannel(); } catch (IOException x) { } } }}; } blockedOn(interruptor); Thread me = Thread.currentThread(); if (me.isInterrupted()) interruptor.interrupt(me); } 複製程式碼
-
如果執行緒阻塞在Selector,執行它的 wakeup方法,因而selector會立即返回,同時會設定中斷標誌
//AbstractSelector中: protected final void begin() { if (interruptor == null) { interruptor = new Interruptible() { public void interrupt(Thread ignore) { //執行wakeup,Selector立即返回 AbstractSelector.this.wakeup(); }}; } AbstractInterruptibleChannel.blockedOn(interruptor); Thread me = Thread.currentThread(); if (me.isInterrupted()) interruptor.interrupt(me); } 複製程式碼
-
如果執行緒阻塞在wait/join/sleep,執行緒的中斷標誌會被清除,並丟擲InterruptedException
-
非上述三種情況,僅設定中斷標誌
可以看出呼叫interrupt並不意味著立即停止目標執行緒正在進行的工作,而只是傳遞了請求中斷的訊息
interrupted
清除當前執行緒的中斷狀態,並返回之前的值。它實際執行的就是當前執行緒的isInterrupted(true)
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
複製程式碼
假設當前執行緒是中斷的,此時呼叫會返回true,如果在下次呼叫之前沒有中斷,此時呼叫會返回false
isInterrupted
返回目標執行緒的中斷狀態,只有執行緒狀態是中斷才會返回true,其它時候返回false
public boolean isInterrupted() {
return isInterrupted(false);
}
複製程式碼
可以看到interrupted和isInterrupted 呼叫的都是isInterrupted方法,只不過引數不一樣。它的引數實際代表的是是否要清除中斷標記,為true也就清除,在java中的定義如下
private native boolean isInterrupted(boolean ClearInterrupted);
複製程式碼
參考linux上的實現為
```
bool os::is_interrupted(Thread* thread, bool clear_interrupted) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
bool interrupted = osthread->interrupted();
if (interrupted && clear_interrupted) {
osthread->set_interrupted(false); //如果中斷了,並且要清除中斷標記,就改變終端標記
// consider thread->_SleepEvent->reset() ... optional optimization
}
return interrupted;
}
```
複製程式碼
響應中斷 - 處理InterruptedException
一般策略如下
- 傳遞異常,使當前方法也成為可中斷的
- 恢復中斷狀態,使得呼叫棧中的上層程式碼能夠對其進行處理
處理不可中斷的阻塞
並非所有的可阻塞方法或者阻塞機制都能響應中斷,停止執行緒的方法類似於中斷
- Java.io中的Socket I/O。InputStream和OutputStream中的read和write等不會響應中斷,可以關閉底層的套接字丟擲SocketException
- Java.io中的同步I/O。大多數的標準的channel都實現了InterruptibleChannel,它內部一般都是丟擲ClosedByInterruptException,並關閉鏈路
- Selector的非同步I/O。阻塞在了Selector.select,通過呼叫wakeup或者close來提前返回。
- 獲取某個鎖。由於執行緒等待某個內建鎖,它會認為自己能等到,所以不會處理中斷,通過Lock的lockInterruptibly可以同時實現等待鎖並且響應中斷
Thread.stop本身是不安全的。停止一個執行緒會釋放它所有的鎖的監視器,如果有任何一個受這些監視器保護的物件出現了狀態不一致,其它的執行緒也會以不一致的狀態檢視這個物件,其它執行緒在這個物件上的任何操作都是無法預料的 為什麼廢棄了Thread.stop
關閉
應用程式準備退出時,這些服務所擁有的執行緒也應該結束。 ExecutorService提供了兩種方法:shutdown和shutdownNow
- shutdown在執行完佇列中的所有任務之後,才關閉,它並不會接收新的任務
- shutdownNow則是立馬關閉正在執行的任務,並返回還沒有開始的任務