Two-phase Termination
January 12, 2022在執行緒正在忙著處理事情時,如果你想停止執行緒,該怎麼做呢?例如,〈Worker Thread〉中的 Worker
,執行一個無窮的任務迴圈,若不再需要某個 Worker
實例了,你要怎麼停止任務迴圈呢?
兩階段終止
如果執行緒處於正在運作中,會有中途停止執行緒的需求,那麼你就應該考量執行緒真正結束前,必須滿足哪些條件。
例如,〈Worker Thread〉的 Worker
,是處於任務迴圈,當 Worker
取得任務且執行中,這時若收到了中止 Worker
的需求,就直接透過 Thread
的 stop
,強制中斷執行緒的話,那麼 Worker
目前的任務可能處於不完整的狀態。
你也許不希望有這類不完整的狀態,因此決定執行緒不該馬上終止,若 Worker
執行任務中,應該讓它完成,在下一次任務迴圈開始前終止迴圈:
class Worker extends Thread {
private boolean isWorkable = true;
private BlockingQueue<Runnable> runnables;
Worker(BlockingQueue<Runnable> runnables) {
this.runnables = runnables;
}
public void run() {
while(isWorkable) {
try {
runnables.take().run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
void terminate() {
isWorkable = false;
}
}
isWorkable
決定了是否執行 while
迴圈本體,呼叫 terminate
前是運作任務的階段,呼叫 terminate
後是善後任務的階段,就這邊的範例來說,就是讓 Worker
處理中的任務執行完畢。
interrupt?
執行緒可能在任務處理過程進入等待狀態,如果想在送出停止需求後,讓進入等待狀態的執行緒醒來,就 Java 而言,可以執行 Thread
的 interrupt
方法,執行緒會離開等待狀態,並從等待處拋出 InterruptedException
。
你得考量 InterruptedException
要如何處理,就 Worker
來說,可能會是在取得任務的過程,因為沒有任務而等待,因此處理上相對而言比較單純:
class Worker extends Thread {
private boolean isWorkable = true;
private BlockingQueue<Runnable> runnables;
Worker(BlockingQueue<Runnable> runnables) {
this.runnables = runnables;
}
private Optional<Runnable> task() {
try {
return Optional.of(runnables.take());
} catch (InterruptedException e) {
...logging?
}
return Optional.empty();
}
public void run() {
while(isWorkable) {
Optional<Runnable> t = task();
if(t.isPresent()) {
t.get().run();
}
}
}
void terminate() {
isWorkable = false;
terminate();
}
}
也就是因為等待任務期間發生 InterruptedException
的話,或許下個日誌,然後傳回 Optional.empty()
,至於任務迴圈中,若沒有任務的話就不執行。
至於 Runnable
本身封裝的任務中,若會有某流程導致執行緒進入等待狀態,InterruptedException
會在該處拋出,那麼該任務要自己負責處理 InterruptedException
,封裝任務時,若捕捉到 InterruptedException
,必須思考執行緒是因哪些條件被迫中斷,才會離開等待狀態,這時要思考該做哪些收尾動作,像是清除執行緒使用的資源之類,各種情境下的處理方式,絕對不要將之私吞,什麼都不處理。