多線程與併發-線程池-ThreadPoolExecutor

多線程與併發-線程池-ThreadPoolExecutor

概述:
線程池這東西很常見,使用的原因更好的對線程進行管理,就是減少線程創建銷燬的開銷。
雖然有幾個現成的創建方法,但很多公司都不建議使用,而要求一定要通過ThreadPoolExecutor來創建,明確運行規則,指定更合適的參數。
這是一個參數最全的重載方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

一共有7個參數
corePoolSize,核心線程數
當有任務進來的時候,如果當前線程數未達到corePoolSize個數,則創建核心線程。
特點:

  • 當線程數未達到最大值的時候,新任務進來,即時有空閒線程也不會複用,仍然創建新的線程。
  • 核心線程一般不會被銷燬,即使處於空閒狀態,但是可以通過allowCoreThreadTimeOut(boolean value) 設置爲 true ,這時核心線程的空閒時間超過keepAliveTime設定的時間時會被銷燬

maximumPoolSize,最大線程池大小
線程池的最大的大小。
當核心線程已滿,並且阻塞隊列已滿時,將判斷當前線程數量是否達到最大值,如果未達到則創建一個非核心線程加入線程池,當線程數量>corePoolSize的時候,如果有線程的空閒時間超過keepAliveTime的時候將被銷燬

keepAliveTime,超時時間
就是上述兩個參數中提到的空閒時間,也就是線程最大空閒時間,默認用於非核心線程,通過 allowCoreThreadTimeOut(boolean value) 方法設置後,也會用於核心線程。(當然線程池中並沒有對線程進行標記是否爲核心線程,當數量超過核心線程數時,任意線程只有空閒時間超過設定值就會被銷燬)。
當設置爲0時,直接銷燬非核心線程
unit,時間單位
這個參數與keepAliveTime是一起的,設定的就是超時時間的時間單位,秒、分、時等。

workQueue,阻塞隊列
當核心線程池沒有空閒時,將把任務任務加入阻塞隊列。這個參數對線程池執行策略有很大影響。
有界隊列:隊列長度有上限,當核心線程已滿時,新任務加入阻塞隊列,阻塞隊列已滿時將根據maximumPoolSize判斷是否創建新的線程加入線程池,當達到最大值時,將根據拒絕策略處理後續的任務。
無界隊列:隊列沒有上限,也就是意味着隊列永遠不會達到上限,也就是說永遠不會創建非核心線程,任務的處理速度有限。同時新來任務可以無限的向隊列中添加。(警惕任務隊列無限堆積的風險)

名稱 是否有界 結構 默認值長度 最大值 備註
ArrayBlockingQueue 有界 數組 初始化指定大小
LinkedBlockingQueue 有界 鏈表 Integer.MAX_VALUE Integer.MAX_VALUE 出隊和入隊使用兩把鎖來實現
PriorityBlockingQueue 無界 數組,堆 11 Integer.MAX_VALUE-8 支持優先級但不能保證同優先級元素的順序
DelayQueue 無界 PriorityQueue 11 創建元素時可以指定多久才能從隊列中獲取當前元素
LinkedTransferQueue 無界 鏈表 CAS實現
LinkedBlockingDeque 無界 鏈表 雙向阻塞隊列
SynchronousQueue 隊列(公平),棧(非公平) 0 內部沒有任何存放元素的能力 CAS實現,對本人這有點像生成者消費者中鎖的通信

threadFactory,線程工廠
指定線程池中線程的創建方式,用於實現生成線程的方式、定義線程名格式、是否後臺執行等等,可以用 Executors.defaultThreadFactory() 默認的實現即可,也可以用 Guava 等三方庫提供的方法實現,如果有特殊要求的話可以自己定義。它最重要的地方應該就是定義線程名稱的格式,便於排查問題了吧。(可以把業務請求編號等信息作爲線程名稱)。

handler,拒絕策略
當沒有空閒的線程(線程池達到最大值maximumPoolSize)處理任務,並且等待隊列已滿(當然這隻對有界隊列有效),再有新任務進來的話,就要做一些取捨了,而這個參數就是指定取捨策略的,有下面四種策略可以選擇:

ThreadPoolExecutor.AbortPolicy//直接拋出異常,這是默認策略; 
ThreadPoolExecutor.DiscardPolicy//直接丟棄任務,但是不拋出異常。 
ThreadPoolExecutor.DiscardOldestPolicy//丟棄隊列最前面的任務,然後將新來的任務加入等待隊列
ThreadPoolExecutor.CallerRunsPolicy//由線程池所在的線程處理該任務,比如在 main 函數中創建線程池,如果執行此策略,將有 main 線程來執行該任務

現有方法創建線程池(不提倡):
雖然並不提倡用 Executors 中的方法來創建線程池,但還是用他們來講一下幾種線程池的原理。
先看一下一部分源碼(有部分重載方法沒有展示,有threadFactory的參數重載方法):
newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

newScheduledThreadPool
繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,週期性任務調度的類實現。關於週期調度的暫不做展開。

以上幾種總結一下:

方法 corePoolSize maximumPoolSize keepAliveTime unit workQueue threadFactory handler
newFixedThreadPool nThreads nThreads 0L TimeUnit.MILLISECONDS LinkedBlockingQueue threadFactory
newSingleThreadExecutor 1 1 0L TimeUnit.MILLISECONDS LinkedBlockingQueue threadFactory
newCachedThreadPool 0 Integer.MAX_VALUE 60L TimeUnit.SECONDS SynchronousQueue threadFactory

以上由ThreadPoolExecutor實現,只是參數有所不同。
不推薦使用的原因:

  • newFixedThreadPool
    由於使用默認使用的是 LinkedBlockingQueue 作爲等待隊列,這是一個無界隊列,這也是使用它的風險所在,除非你能保證提交的任務不會無節制的增長,否則不要使用無界隊列,這樣有可能造成等待隊列無限增加,造成 OOM。
  • newSingleThreadExecutor
    與newFixedThreadPool類似,只是設定了線程池大小爲1,理由同上。
  • newCachedThreadPool
    關鍵點就在於它使用 SynchronousQueue 作爲等待隊列,它不會保留任務,新任務進來後,直接創建臨時線程處理,這樣一來,也就容易造成無限制的創建線程,造成 OOM
  • newScheduledThreadPool
    計劃型線程池,可以設置固定時間的延時或者定期執行任務,同樣是看線程池中有沒有空閒線程,如果有,直接拿來使用,如果沒有,則新建線程加入池。使用的是 DelayedWorkQueue 作爲等待隊列,這中類型的隊列會保證只有到了指定的延時時間,纔會執行任務
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章