線程池,顧名思義就是裝線程的池子。其用途是爲了幫咱們重複管理線程,避免建立大量的線程增長開銷,提升響應速度。 java
做爲一個嚴謹的攻城獅,不會但願別人看到咱們的代碼就開始吐槽,new Thread().start()會讓代碼看起來混亂臃腫,而且很差管理和維護,那麼咱們就須要用到了線程池。編程
在編程中常常會使用線程來異步處理任務,可是每一個線程的建立和銷燬都須要必定的開銷。若是每次執行一個任務都須要開一個新線程去執行,則這些線程的建立和銷燬將消耗大量的資源;而且線程都是「各自爲政」的,很難對其進行控制,更況且有一堆的線程在執行。線程池爲咱們作的,就是線程建立以後爲咱們保留,當咱們須要的時候直接拿來用,省去了重複建立銷燬的過程。異步
//五個參數的構造函數
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) //六個參數的構造函數-1 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) //六個參數的構造函數-2 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) //七個參數的構造函數 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 複製代碼
雖然參數多,只是看着嚇人,其實很好理解,下面會一一解答。函數
咱們拿最多參數的來講:spa
核心線程:在建立完線程池以後,核心線程先不建立,在接到任務以後建立核心線程。而且會一直存在於線程池中(即便這個線程啥都不幹),有任務要執行時,若是核心線程沒有被佔用,會優先用核心線程執行任務。數量通常狀況下設置爲CPU核數的二倍便可。線程
線程總數=核心線程數+非核心線程數3d
非核心線程:簡單理解,即核心線程都被佔用,但還有任務要作,就建立非核心線程code
這個參數能夠理解爲,任務少,但池中線程多,非核心線程不能白養着,超過這個時間不工做的就會被幹掉,可是核心線程會保留。cdn
TimeUnit是一個枚舉類型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小時
DAYS : 天blog
默認狀況下,任務進來以後先分配給核心線程執行,核心線程若是都被佔用,並不會馬上開啓非核心線程執行任務,而是將任務插入任務隊列等待執行,核心線程會從任務隊列取任務來執行,任務隊列能夠設置最大值,一旦插入的任務足夠多,達到最大值,纔會建立非核心線程執行任務。
常見的workQueue有四種:
1.SynchronousQueue:這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它,若是全部線程都在工做怎麼辦?那就新建一個線程來處理這個任務!因此爲了保證不出現<線程數達到了maximumPoolSize
而不能新建線程>的錯誤,使用這個類型隊列的時候,maximumPoolSize
通常指定成Integer.MAX_VALUE,即無限大
2.LinkedBlockingQueue:這個隊列接收到任務的時候,若是當前已經建立的核心線程數小於線程池的核心線程數上限,則新建線程(核心線程)處理任務;若是當前已經建立的核心線程數等於核心線程數上限,則進入隊列等待。因爲這個隊列沒有最大值限制,即全部超過核心線程數的任務都將被添加到隊列中,這也就致使了maximumPoolSize
的設定失效,由於總線程數永遠不會超過corePoolSize
3.ArrayBlockingQueue:能夠限定隊列的長度,接收到任務的時候,若是沒有達到corePoolSize
的值,則新建線程(核心線程)執行任務,若是達到了,則入隊等候,若是隊列已滿,則新建線程(非核心線程)執行任務,又若是總線程數到了maximumPoolSize
,而且隊列也滿了,則發生錯誤,或是執行實現定義好的飽和策略
4.DelayQueue:隊列內元素必須實現Delayed接口,這就意味着你傳進去的任務必須先實現Delayed接口。這個隊列接收到任務時,首先先入隊,只有達到了指定的延時時間,纔會執行任務
能夠用線程工廠給每一個建立出來的線程設置名字。通常狀況下無須設置該參數。
這是當任務隊列和線程池都滿了時所採起的應對策略,默認是AbordPolicy, 表示沒法處理新任務,並拋出 RejectedExecutionException 異常。此外還有3種策略,它們分別以下。
(1)CallerRunsPolicy:用調用者所在的線程來處理任務。此策略提供簡單的反饋控制機制,可以減緩新任務的提交速度。
(2)DiscardPolicy:不能執行的任務,並將該任務刪除。
(3)DiscardOldestPolicy:丟棄隊列最近的任務,並執行當前的任務。
別暈,接下來上圖,相信結合圖你能大徹大悟~
說了半天原理,接下來就要用了,java爲咱們提供了4種線程池FixedThreadPool
、CachedThreadPool
、SingleThreadExecutor
、ScheduledThreadPool
,幾乎能夠知足咱們大部分的須要了:
可重用固定線程數的線程池,超出的線程會在隊列中等待,在Executors類中咱們能夠找到建立方式:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
複製代碼
FixedThreadPool
的corePoolSize
和maximumPoolSize
都設置爲參數nThreads,也就是隻有固定數量的核心線程,不存在非核心線程。keepAliveTime
爲0L表示多餘的線程馬上終止,由於不會產生多餘的線程,因此這個參數是無效的。FixedThreadPool
的任務隊列採用的是LinkedBlockingQueue。
public static void main(String[] args) {
// 參數是要線程池的線程最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
}
複製代碼
CachedThreadPool是一個根據須要建立線程的線程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
複製代碼
CachedThreadPool
的corePoolSize
是0,maximumPoolSize
是Int的最大值,也就是說CachedThreadPool
沒有核心線程,所有都是非核心線程,而且沒有上限。keepAliveTime
是60秒,就是說空閒線程等待新任務60秒,超時則銷燬。此處用到的隊列是阻塞隊列SynchronousQueue
,這個隊列沒有緩衝區,因此其中最多隻能存在一個元素,有新的任務則阻塞等待。
SingleThreadExecutor
是使用單個線程工做的線程池。其建立源碼以下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
複製代碼
咱們能夠看到總線程數和核心線程數都是1,因此就只有一個核心線程。該線程池才用鏈表阻塞隊列LinkedBlockingQueue
,先進先出原則,因此保證了任務的按順序逐一進行。
ScheduledThreadPool
是一個能實現定時和週期性任務的線程池,它的建立源碼以下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
複製代碼
這裏建立了ScheduledThreadPoolExecutor
,繼承自ThreadPoolExecutor
,主要用於定時延時或者按期處理任務。ScheduledThreadPoolExecutor
的構造以下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
複製代碼
能夠看出corePoolSize
是傳進來的固定值,maximumPoolSize
無限大,由於採用的隊列DelayedWorkQueue
是無解的,因此maximumPoolSize
參數無效。該線程池執行以下:
scheduleAtFixedRate
或者
scheduleWithFixedDelay
方法時,會向
DelayedWorkQueue
添加一個實現
RunnableScheduledFuture
接口的
ScheduledFutureTask
(任務的包裝類),並會檢查運行的線程是否達到
corePoolSize
。若是沒有則新建線程並啓動
ScheduledFutureTask
,而後去執行任務。若是運行的線程達到了
corePoolSize
時,則將任務添加到
DelayedWorkQueue
中。
DelayedWorkQueue
會將任務進行排序,先要執行的任務會放在隊列的前面。在跟此前介紹的線程池不一樣的是,當執行完任務後,會將
ScheduledFutureTask
中的
time
變量改成下次要執行的時間並放回到
DelayedWorkQueue
中。
通常須要根據任務的類型來配置線程池大小:
若是是CPU密集型任務,就須要儘可能壓榨CPU,參考值能夠設爲 NCPU+1
若是是IO密集型任務,參考值能夠設置爲2*NCPU
固然,這只是一個參考值,具體的設置還須要根據實際狀況進行調整,好比能夠先將線程池大小設置爲參考值,再觀察任務運行狀況和系統負載、資源利用率來進行適當調整。
java爲咱們提供的線程池就介紹到這了,牆裂建議你們仍是動手去敲一敲,畢竟實踐過內心纔有底。