天天看點

關閉線程池 shutdown 和 shutdownNow 的差別?

前言

本章分為兩個議題

  • 如何正确關閉線程池
  • shutdown 和 shutdownNow 的差別

1.線程池示例

public class ShutDownThreadPoolDemo {

    private ExecutorService service = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        new ShutDownThreadPoolDemo().executeTask();
    }

    public void executeTask() {
        for (int i = 0; i < 100; i++) {
            service.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "->執行");
            });
        }
    }

}      

執行結果

pool-1-thread-2->執行
pool-1-thread-3->執行
pool-1-thread-1->執行
pool-1-thread-4->執行
pool-1-thread-5->執行
pool-1-thread-6->執行
...      

執行完成之後,主線程會一直阻塞,那麼如何關閉線程池呢?本章介紹 5 種在 ThreadPoolExecutor 中涉及關閉線程池的方法,如下所示

void shutdown

boolean isShutdown

boolean isTerminated

boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException

ListshutdownNow

2.shutdown

第一種方法叫作 shutdown(),它可以安全地關閉一個線程池,調用 shutdown() 方法之後線程池并不是立刻就被關閉,因為這時線程池中可能還有很多任務正在被執行,或是任務隊列中有大量正在等待被執行的任務,調用 shutdown() 方法後線程池會在執行完正在執行的任務和隊列中等待的任務後才徹底關閉。

調用 shutdown() 方法後如果還有新的任務被送出,線程池則會根據拒絕政策直接拒絕後續新送出的任務。

這段源碼位置(jdk 1.8 版本)

java.util.concurrent.ThreadPoolExecutor#execute

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 線程池中的線程比核心線程數少 
    if (workerCountOf(c) < corePoolSize) {
        // 建立一個核心線程執行任務
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 核心線程已滿,但是任務隊列未滿,添加到隊列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 任務成功添加到隊列以後,再次檢查是否需要添加新的線程,因為已存在的線程可能被銷毀了
        if (! isRunning(recheck) && remove(command))
            // 如果線程池處于非運作狀态,并且把目前的任務從任務隊列中移除成功,則拒絕該任務
            reject(command);
        else if (workerCountOf(recheck) == 0)
            // 如果之前的線程已經被銷毀完,建立一個非核心線程
            addWorker(null, false);
    }
    // 核心線程池已滿,隊列已滿,嘗試建立一個非核心新的線程
    else if (!addWorker(command, false))
        // 如果建立新線程失敗,說明線程池關閉或者線程池滿了,拒絕任務
        reject(command);
}      

1373 行 if (! isRunning(recheck) && remove(command)) 如果線程池被關閉,将目前的任務從任務隊列中移除成功,并拒絕該任務

1378 行 else if (!addWorker(command, false)) 如果建立新線程失敗,說明線程池關閉或者線程池滿了,拒絕任務。

3.isShutdown

第二個方法叫作 isShutdown(),它可以傳回 true 或者 false 來判斷線程池是否已經開始了關閉工作,也就是是否執行了 shutdown 或者 shutdownNow 方法。

這裡需要注意,如果調用 isShutdown() 方法的傳回的結果為 true 并不代表線程池此時已經徹底關閉了,這僅僅代表線程池開始了關閉的流程,也就是說,此時可能線程池中依然有線程在執行任務,隊列裡也可能有等待被執行的任務。

4.isTerminated

第三種方法叫作 isTerminated(),這個方法可以檢測線程池是否真正“終結”了,這不僅代表線程池已關閉,同時代表線程池中的所有任務都已經都執行完畢了。

比如我們上面提到的情況,如果此時已經調用了 shutdown 方法,但是還有任務沒有執行完,那麼此時調用 isShutdown 方法傳回的是 true,而 isTerminated 方法則會傳回 false。

直到所有任務都執行完畢了,調用 isTerminated() 方法才會傳回 true,這表示線程池已關閉并且線程池内部是空的,所有剩餘的任務都執行完畢了。

5.awaitTermination

第四個方法叫作 awaitTermination(),它本身并不是用來關閉線程池的,而是主要用來判斷線程池狀态的。

比如我們給 awaitTermination 方法傳入的參數是 10 秒,那麼它就會陷入 10 秒鐘的等待,直到發生以下三種情況之一:

等待期間(包括進入等待狀态之前)線程池已關閉并且所有已送出的任務(包括正在執行的和隊列中等待的)都執行完畢,相當于線程池已經“終結”了,方法便會傳回 true 等待逾時時間到後,第一種線程池“終結”的情況始終未發生,方法傳回 false 等待期間線程被中斷,方法會抛出 InterruptedException 異常 調用 awaitTermination 方法後目前線程會嘗試等待一段指定的時間,如果在等待時間内,線程池已關閉并且内部的任務都執行完畢了,也就是說線程池真正“終結”了,那麼方法就傳回 true,否則逾時傳回 fasle。

6.shutdownNow

最後一個方法是 shutdownNow(),它和 shutdown() 的差別就是多了一個 Now,表示立刻關閉的意思,不推薦使用這一種方式關閉線程池。

在執行 shutdownNow 方法之後,首先會給所有線程池中的線程發送 interrupt 中斷信号,嘗試中斷這些任務的執行,然後會将任務隊列中正在等待的所有任務轉移到一個 List 中并傳回,我們可以根據傳回的任務 List 來進行一些補救的操作,例如記錄在案并在後期重試。

shutdownNow 源碼如下:

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}      

interruptWorkers

讓每一個已經啟動的線程都中斷,這樣線程就可以在執行任務期間檢測到中斷信号并進行相應的處理,提前結束任務

7.shutdown 和 shutdownNow 的差別?

shutdown 會等待線程池中的任務執行完成之後關閉線程池,而 shutdownNow 會給所有線程發送中斷信号,中斷任務執行,然後關閉線程池

shutdown 沒有傳回值,而 shutdownNow 會傳回關閉前任務隊列中未執行的任務集合(List)