1. 程式人生 > >ThreadPoolExecutor類詳解

ThreadPoolExecutor類詳解

JDK的ThreadPoolExecutor類實現了執行緒池,在它的建構函式中,有五個引數,讓我們看看這五個引數具體作用是什麼。

  • corePoolSize,核心執行緒池數量。
    預設情況下會一直存活,當請求進來時,如果當時的存活執行緒數小於核心執行緒數量,就會去新建立執行緒來處理這個請求,即使有其他執行緒是空閒的。在allowCoreThreadTimeout引數設定會true時,核心執行緒也會超時退出,預設是false超時不退出。
  • maxPoolSize,存活執行緒最大數量。
    當執行緒數大於或等於核心執行緒,且任務佇列已滿時,執行緒池會繼續建立新的執行緒處理請求,直到maxPoolSize的限制數量。如果當前存活的執行緒已經到了maxPoolSize的限制數量,且任務佇列已滿,執行緒池會拒絕處理任務並丟擲異常。
  • keepAliveTime,執行緒可空閒時間。
    當執行緒空閒時間打到keepAliveTime時,執行緒會退出,直到存活執行緒數量等於核心執行緒數量。如果allowCoreThreadTimeout引數設定會true時,核心執行緒也會退出,直到執行緒數為0。
  • TimeUnit,空閒執行緒的保留時間單位。
  • workQueue ,快取工作佇列,用來快取等待執行的任務。
  • queueCapacity,任務佇列容量。
  • handler:表示當拒絕處理任務時的策略。
    有四種取值:
    (1)ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常,預設此配置。
    (2)ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
    (3)ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)。
    (4)ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務。

任務佇列的選用
任務佇列一般選用LinkedBlockingQueue和Synchronous。注意當使用LinkedBlockingQueue時,一定要設定佇列的大小,否則會預設設定佇列的大小為Integer.MaxValue,可能會造成資源耗盡。

執行緒池的關閉
ThreadPoolExecutor提供了兩個方法去關閉執行緒池。
shutdown():不會立即終止執行緒池,而是要等所有任務快取佇列中的任務都執行完後才終止,但再也不會接受新的任務。
shutdownNow():立即終止執行緒池,並嘗試打斷正在執行的任務,並且清空任務快取佇列,返回尚未執行的任務。

程式碼驗證


我們來寫一個測試類驗證一下

     public static void main(String[] args) {
            int corePoolSize = 2;
            int maximumPoolSize = 5;
            int keepAliveTime = 10;
            BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);

            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
                    TimeUnit.SECONDS, workQueue);
            try {
                test(threadPoolExecutor, 3);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            } finally {
                //最後關閉執行緒池
                threadPoolExecutor.shutdown();
            }

        }

        public static void test(ThreadPoolExecutor threadPoolExecutor, int taskCount) {
            for (int i = 0; i < taskCount; i++) {
                threadPoolExecutor.submit(new TaskTest());
                System.out.println("總任務數:" + threadPoolExecutor.getTaskCount() + ",已完成任務數:" + threadPoolExecutor.getCompletedTaskCount()
                        + ",執行緒池執行緒數目:" + threadPoolExecutor.getPoolSize() + ",處於執行狀態的數目:" + threadPoolExecutor.getActiveCount());
                System.out.println("快取佇列任務數:"+threadPoolExecutor.getQueue().size());
            }
        }

        static class TaskTest implements Runnable {

            @Override
            public void run() {
                try {
                    //為了更好的效果,休眠一秒
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "執行完畢");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

我們先把核心執行緒數設定成2,最大執行緒數設定成5,快取工作佇列設定成2,然後把我們要執行的任務書設定成3個,看下執行的結果是什麼。

總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢
pool-1-thread-2執行完畢

從最後執行完畢時列印的執行緒名稱可知,最多隻有兩個執行緒在執行任務(核心執行緒數為2),而當前的總任務數達到核心任務數後,新的任務會被放到快取佇列中,因為快取佇列還沒滿,所以執行緒池不會去建立新的執行緒去處理任務。那我們再來試一下快取佇列滿了之後會怎麼樣,我們把任務數設定成5,結果如下。

總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
總任務數:5,已完成任務數:0,執行緒池執行緒數目:3,處於執行狀態的數目:2
快取佇列任務數:2
pool-1-thread-1執行完畢
pool-1-thread-2執行完畢
pool-1-thread-3執行完畢
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢

果然,第三個執行緒出來了,當快取佇列也滿了的時候,執行緒池會建立新的執行緒去處理任務,當然這個執行緒數也是會被我們設定的maximumPoolSize最大執行緒數限制的,最大允許執行緒數為5個,再加上快取佇列的兩個,所以最大可處理為7個任務,當第八個任務來了的時候會怎麼樣呢,我們來試試,把任務數提高到8個,結果如下。

總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
總任務數:5,已完成任務數:0,執行緒池執行緒數目:3,處於執行狀態的數目:3
快取佇列任務數:2
總任務數:6,已完成任務數:0,執行緒池執行緒數目:4,處於執行狀態的數目:4
快取佇列任務數:2
總任務數:7,已完成任務數:0,執行緒池執行緒數目:5,處於執行狀態的數目:5
快取佇列任務數:2
Task java.util.concurrent.FutureTask@68de145 rejected from java.util.concurrent.ThreadPoolExecutor@27fa135a[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
pool-1-thread-1執行完畢
pool-1-thread-3執行完畢
pool-1-thread-5執行完畢
pool-1-thread-2執行完畢
pool-1-thread-4執行完畢
pool-1-thread-1執行完畢
pool-1-thread-3執行完畢

最終被處理的任務數為七個,而且丟擲了異常,第八個任務被捨棄掉了(預設ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常)。

我們再來測試一下兩種關閉執行緒池方法的區別,先來試試shoudownNow()方法,在原來的程式碼上改動一下。

test(threadPoolExecutor, 4);
List<Runnable> runnableList = threadPoolExecutor.shutdownNow();
System.out.println("未處理任務數:"+runnableList.size());

結果如下:

總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
未處理任務數:2
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢

關閉執行緒池後返回的未處理任務數為2,最終也只處理了前兩條任務。我們再來試試shutdown()方法。

threadPoolExecutor.shutdown();

結果如下:

總任務數:1,已完成任務數:0,執行緒池執行緒數目:1,處於執行狀態的數目:1
快取佇列任務數:0
總任務數:2,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:0
總任務數:3,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:1
總任務數:4,已完成任務數:0,執行緒池執行緒數目:2,處於執行狀態的數目:2
快取佇列任務數:2
pool-1-thread-2執行完畢
pool-1-thread-2執行完畢
pool-1-thread-2執行完畢
pool-1-thread-1執行完畢

四個任務全部執行完成。

今天的總結就到這裡,共勉。