線程池的工作過程

線程池的工作過程

  1. 線程池剛創建時,里面沒有一個線程。任務隊列是作為參數傳進來的。不過,就算隊列里面有任務,線程池也不會馬上執行它們。

  2. 當調用 execute() 方法添加一個任務時,線程池會做如下判斷:

    1. 如果正在運行的線程數量小于 corePoolSize,那么馬上創建線程運行這個任務;
    2. 如果正在運行的線程數量大于或等于 corePoolSize,那么將這個任務放入隊列;
    3. 如果這時候隊列滿了,而且正在運行的線程數量小于 maximumPoolSize,那么還是要創建非核心線程立刻運行這個任務;
    4. 如果隊列滿了,而且正在運行的線程數量大于或等于 maximumPoolSize,那么線程池會拋出異常RejectExecutionException。
  3. 當一個線程完成任務時,它會從隊列中取下一個任務來執行。(這是一個非常巧妙的設計方式,假如我們來設計線程池,可能會有一個任務分派線程,當發現有線程空閑時,就從任務緩存隊列中取一個任務交給空閑線程執行。但是在這里,并沒有采用這樣的方式,因為這樣會要額外地對任務分派線程進行管理,無形地會增加難度和復雜度,這里直接讓執行完任務的線程去任務緩存隊列里面取任務來執行。)

  4. 當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行的線程數大于 corePoolSize,那么這個線程就被停掉。所以線程池的所有任務完成后,它最終會收縮到 corePoolSize 的大小。如果允許為核心池中的線程設置存活時間,那么核心池中的線程空閑時間超過keepAliveTime,線程也會被終止。

線程池狀態

在ThreadPoolExecutor中定義了一個Volatile變量,另外定義了幾個static final變量表示線程池的各個狀態:

volatile int runState;

static final int RUNNING= 0;

static final int SHUTDOWN = 1;

static final int STOP = 2;

static final int TERMINATED = 3;

runState表示當前線程池的狀態,它是一個volatile變量用來保證線程之間的可見性;

下面的幾個static final變量表示runState可能的幾個取值:

  1. 當創建線程池后,初始時,線程池處于RUNNING狀態;
  2. 如果調用了shutdown()方法,則線程池處于SHUTDOWN狀態,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;
  3. 如果調用了shutdownNow()方法,則線程池處于STOP狀態,此時線程池不能接受新的任務,并且會去嘗試終止正在執行的任務;
  4. 當線程池處于SHUTDOWN或STOP狀態,并且所有工作線程已經銷毀,任務緩存隊列已經清空或執行結束后,線程池被設置為TERMINATED狀態。

ThreadPoolExecutor類中的一些比較重要成員變量

  1. private final BlockingQueue<Runnable> workQueue; //任務緩存隊列,用來存放等待執行的任務

  2. private final ReentrantLock mainLock = new ReentrantLock(); //線程池的主要狀態鎖,對線程池狀態(比如線程池大小
    //、runState等)的改變都要使用這個鎖

  3. private final HashSet<Worker> workers = new HashSet<Worker>(); //用來存放工作集

  4. private volatile long keepAliveTime;//線程存活時間

  5. private volatile boolean allowCoreThreadTimeOut; //是否允許為核心線程設置存活時間

  6. private volatile int corePoolSize; //核心池的大小(即線程池中的線程數目大于這個參數時,提交的任務會被放進任務緩存隊列)

  7. private volatile int maximumPoolSize; //線程池最大能容忍的線程數

  8. private volatile int poolSize; //線程池中當前的線程數

  9. private volatile RejectedExecutionHandler handler; //任務拒絕策略

  10. private volatile ThreadFactory threadFactory; //線程工廠,用來創建線程

  11. private int largestPoolSize; //用來記錄線程池中曾經出現過的最大線程數

  12. private long completedTaskCount; //用來記錄已經執行完畢的任務個數

任務緩存隊列及排隊策略

在前面我們多次提到了任務緩存隊列,即workQueue,它用來存放等待執行的任務。

workQueue的類型為BlockingQueue<Runnable>,通常可以取下面三種類型:

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

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

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

任務拒絕策略

當線程池的任務緩存隊列已滿并且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會采取任務拒絕策略,通常有以下四種策略:

  1. ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常。
  2. ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
  3. ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)
  4. ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

線程池的關閉

ThreadPoolExecutor提供了兩個方法,用于線程池的關閉,分別是shutdown()和shutdownNow(),其中:

  1. shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完后才終止,但再也不會接受新的任務
  2. shutdownNow():立即終止線程池,并嘗試打斷正在執行的任務,并且清空任務緩存隊列,返回尚未執行的任務

線程池容量的動態調整

ThreadPoolExecutor提供了動態調整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

  1. setCorePoolSize:設置核心池大小
  2. setMaximumPoolSize:設置線程池最大能創建的線程數目大小

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

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

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

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

  4. 當然,這只是一個參考值,具體的設置還需要根據實際情況進行調整,比如可以先將線程池大小設置為參考值,再觀察任務運行情況和系統負載、資源利用率來進行適當調整。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容