天天看點

[Java并發程式設計實戰]管理 Executor 生命周期的幾個方法

燕雀安知鴻鹄之志?———《史記·陳涉世家》

意思是燕雀怎麼能知道鴻鹄的遠大志向,比喻平凡的人哪裡知道英雄人物的志向。

前文說了 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();
            }            
        }
    }
}           

執行結果如下:

[Java并發程式設計實戰]管理 Executor 生命周期的幾個方法

從執行結果看,調用 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");
}           

執行結果如下:

[Java并發程式設計實戰]管理 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.
 *
           

本文完結。

繼續閱讀