燕雀安知鴻鹄之志?———《史記·陳涉世家》
意思是燕雀怎麼能知道鴻鹄的遠大志向,比喻平凡的人哪裡知道英雄人物的志向。
前文說了 Exectuor 的具體用法,卻沒有說到如何停止一個任務的執行。是以,本篇文章就來講解一下 Executor 的生命周期管理。
首先,看下這個架構生命周期相關的主要方法。
void shutdown();//平緩關閉
List<Runnable> shutdownNow(); //強制關閉
boolean isShutdown(); //是否關閉
boolean isTerminated(); //是否終止
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; //timeout時間後是否終止
我們來寫例子驗證他們作用吧。
建立一個耗時的任務類,模拟下載下傳圖檔,耗時 5s。後面線程池架構都執行這個任務類。
//建立一個耗時任務類
static class ImageDown implements Runnable{
@Override
public void run() {
try {
//正在下載下傳圖檔
System.out.println("downing image");
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
隻要這個任務被執行,就會列印 log: downing image。
我們先來看看 shutdown, awaitTermination 的用法。下面這段代碼,會建立 5 個線程,執行10個任務。每個任務耗時5s。把所有任務送出給 executorservice 後,調用 shutdown 關閉。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ExecutorThreadPollTest {
//線程個數
private static final int THREAD_SIZE = 5;
private static ExecutorService executor;
public static void main(String[] args) {
//建立一個線程池
executor = Executors.newFixedThreadPool(THREAD_SIZE);
//送出10個任務
for(int i = 0; i < THREAD_SIZE * 2; i++) {
executor.submit(new ImageDown());
}
//關閉線程池
cancelTask();
try {
//線城池是否關閉,1s判斷一次
while(!executor.awaitTermination(1, TimeUnit.SECONDS)) {
System.out.println("Thread Pool is Running");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Thread Pool is Terminated");
}
//關閉線程池
static void cancelTask() {
//關閉線程池
executor.shutdown();
System.out.println("shutdown executor");
}
//建立一個耗時任務類
static class ImageDown implements Runnable{
@Override
public void run() {
try {
System.out.println("downing image");
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
執行結果如下:
從執行結果看,調用 shutdown 後,任務并沒有直接取消,而是等待隊列中的所有任務執行完畢才關閉。換句話說, shutdown 是平緩關閉,已經送出的任務會全部執行完再關閉。
另外,awaitTermination 會不斷去輪詢目前 executorservice 的狀态,檢查是否已經關閉。這裡通過設定它的參數,表示 1s 後會去查一次,看看狀态是否變化。若 executorservice 已經關閉,則傳回 true, 否則傳回 false。
假如上面例子中,在調用 shutdown 後,我們繼續送出任務的話,将會被拒絕,抛出異常,整個主線程都會終止。
executor.submit(new ImageDown());
抛出的異常[Shutting down, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task [email protected]75b84c92 rejected from [email protected]6bc7c054[Shutting down, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at ExecutorThreadPollTest.main(ExecutorThreadPollTest.java:23)
那麼,要解決這個問題,可以在送出任務的時候,判斷 executorservice 是否關閉,如果 isShutDown 傳回true, 則不要繼續送出任務。相反,則可以繼續送出任務。
我們修改一下代碼,用shutdownNow來關閉 executorservice.
//關閉線程池
static void cancelTask() {
//關閉線程池
List<Runnable> list = executor.shutdownNow();
System.out.println("is shut down = " + executor.isShutdown());
System.out.println("is Terminated = " + executor.isTerminated());
System.out.println("not running task num = " + list.size());
System.out.println("shutdown executor");
}
執行結果如下:
從這個結果看,可以知道當我們調用 shutdownNow 時,任務立馬被關閉。是以 awaitTermination 傳回的是true, 這個地方沒有列印 log。同時,這個 shutdownNow 函數傳回工作隊列中等待的還未執行的任務。這個還有 5 個任務沒有開始執行。
另外, isShutDown 在調用 shutdown 和 shutdownNow 函數後,會立馬變為 true。
isTermination 判斷目前狀态是否是 TERMINATED ,上面 log 有時列印是 true, 有時是 false。說明從 shutdownNow 到 TERMINATED 這個狀态,中間還有其他狀态的變化跳變,并不是一下子就轉到 TERMINATED。
ExecutorService 的狀态補充說明如下:
* The runState provides the main lifecycle control, taking on values: * * RUNNING: Accept new tasks and process queued tasks * SHUTDOWN: Don't accept new tasks, but process queued tasks * STOP: Don't accept new tasks, don't process queued tasks, * and interrupt in-progress tasks * TIDYING: All tasks have terminated, workerCount is zero, * the thread transitioning to state TIDYING * will run the terminated() hook method * TERMINATED: terminated() has completed * * The numerical order among these values matters, to allow * ordered comparisons. The runState monotonically increases over * time, but need not hit each state. The transitions are: * * RUNNING -> SHUTDOWN * On invocation of shutdown(), perhaps implicitly in finalize() * (RUNNING or SHUTDOWN) -> STOP * On invocation of shutdownNow() * SHUTDOWN -> TIDYING * When both queue and pool are empty * STOP -> TIDYING * When pool is empty * TIDYING -> TERMINATED * When the terminated() hook method has completed * * Threads waiting in awaitTermination() will return when the * state reaches TERMINATED. *
本文完結。