苦苦等待的Java線程池總結終於來了!

一、核心類

  • 頂級接口Executor
  • ExecutorService實現Executor
  • ThreadPoolExecutor間接實現ExecutorService
  • Executors實現類可用於獲取四種配置好的線程池

二、核心流程

  • 如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;
  • 如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;
  • 如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;
  • 如果線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。

三、Executor接口方法

  • execute() 執行任務無返回結果
  • submit() 執行任務有返回結果,返回結果可以通過Future獲取
  • shutdown() 關閉線程池,但是會執行結束之前獲取的任務
  • shutdownNow() 立即關閉線程池,結束所有正在執行的任務,之前接受的所有任務被終止

四、Executors四種靜態方法獲取配置好的線程

Executors.newCachedThreadPool();        //創建一個緩衝池,核心線程數爲0,緩衝池容量大小(最大線程數)爲Integer.MAX_VALUE,任務隊列採用SynchronousQueue,容易發生資源耗盡bug

Executors.newSingleThreadExecutor();   //創建容量爲1的緩衝池,任務隊列採用LinkedBlockingQueue,無界

Executors.newFixedThreadPool(int);    //創建固定容量大小的緩衝池,任務隊列採用LinkedBlockingQueue,無界

源碼實現

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

從它們的具體實現來看,它們實際上也是調用了ThreadPoolExecutor,只不過參數都已配置好了。

newFixedThreadPool創建的線程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

newSingleThreadExecutor將corePoolSize和maximumPoolSize都設置爲1,也使用的LinkedBlockingQueue;

newCachedThreadPool將corePoolSize設置爲0,將maximumPoolSize設置爲Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就創建線程運行,當線程空閒超過60秒,就銷燬線程。

實際中,如果Executors提供的三個靜態方法能滿足要求,就儘量使用它提供的三個方法,因爲自己去手動配置ThreadPoolExecutor的參數有點麻煩,要根據實際任務的類型和數量來進行配置。

另外,如果ThreadPoolExecutor達不到要求,可以自己繼承ThreadPoolExecutor類進行重寫。

五、阻塞隊列策略

阻塞隊列即任務緩存隊列,即workQueue,它用來存放等待執行的任務。workQueue的類型爲BlockingQueue,通常可以取下面三種類型:

1)ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小;

2)LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;

3)synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。

六、任務拒絕策略

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。

ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。

ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)

ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

七、線程池的關閉

volatile int runState;

static final int RUNNING    = 0;

static final int SHUTDOWN   = 1;

static final int STOP       = 2;

static final int TERMINATED = 3;
  • 執行shutdown()方法,會更改線程池的runState爲爲SHUTDOWN,但是線程池會等待之前接受的所有任務執行結束
  • shutdownNow()方法,則線程池處於STOP狀態,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務

八、使用方式

public class Test {
     public static void main(String[] args) {   
         ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                 new ArrayBlockingQueue<Runnable>(5));
         for(int i=0;i<15;i++){
             MyTask myTask = new MyTask(i);
             executor.execute(myTask);
             executor.getQueue().size()+",已執行玩別的任務數目:"+executor.getCompletedTaskCount());
         }
         executor.shutdown();
     }
}

九、如何合理配置線程池的大小

本節來討論一個比較重要的話題:如何合理配置線程池大小,僅供參考。

一般需要根據任務的類型來配置線程池大小:

如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設爲 NCPU+1

如果是IO密集型任務,參考值可以設置爲2*NCPU

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章