一、為什么要用線程池
- 降低資源消耗。通過重復利用已創建的線程降低線程創建、銷毀線程造成的消耗。
- 提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
- 提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配、調優和監控。
二、ThreadPoolExecutor線程池類參數詳解
當線程池任務處理不過來的時候(什么時候認為處理不過來后面描述),可以通過handler指定的策略進行處理,ThreadPoolExecutor提供了四種策略:
- ThreadPoolExecutor.AbortPolicy:丟棄任務并拋出RejectedExecutionException異常;也是默認的處理方式。
- ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋出異常。
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然后重新嘗試執行任務(重復此過程)
- ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
可以通過實現RejectedExecutionHandler接口自定義處理方式。
三、線程池任務執行
1. 添加執行任務
- submit() 該方法返回一個Future對象,可執行帶返回值的線程;或者執行想隨時可以取消的線程。Future對象的get()方法獲取返回值。Future對象的cancel(true/false)取消任務,未開始或已完成返回false,參數表示是否中斷執行中的線程。
- execute() 沒有返回值。
2. 線程池任務提交過程
2.1. 如果此時線程池中的數量小于corePoolSize,即使線程池中的線程都處于空閑狀態,也要創建新的線程來處理被添加的任務。
2.2. 如果此時線程池中的數量等于corePoolSize,但是緩沖隊列workQueue未滿,那么任務被放入緩沖隊列。
2.3. 如果此時線程池中的數量大于等于corePoolSize,緩沖隊列workQueue滿,并且線程池中的數量小于maximumPoolSize,建新的線程來處理被添加的任務。
2.4. 如果此時線程池中的數量大于corePoolSize,緩沖隊列workQueue滿,并且線程池中的數量等于maximumPoolSize,那么通過 handler所指定的策略來處理此任務。
2.5. 當線程池中的線程數量大于 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
總結:處理任務判斷的優先級為 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
注意:
- 當workQueue使用的是×××限隊列時,maximumPoolSize參數就變的無意義了,比如new LinkedBlockingQueue(),或者new ArrayBlockingQueue(Integer.MAX_VALUE);
- 使用SynchronousQueue隊列時由于該隊列沒有容量的特性,所以不會對任務進行排隊,如果線程池中沒有空閑線程,會立即創建一個新線程來接收這個任務。maximumPoolSize要設置大一點。
- 核心線程和最大線程數量相等時keepAliveTime無作用.
3. 線程池關閉
3.1. shutdown() 不接收新任務,會處理已添加任務
3.2. shutdownNow() 不接受新任務,不處理已添加任務,中斷正在處理的任務
4. 常用隊列介紹
4.1. ArrayBlockingQueue: 這是一個由數組實現的容量固定的有界阻塞隊列.
4.2. SynchronousQueue: 沒有容量,不能緩存數據;每個put必須等待一個take; offer()的時候如果沒有另一個線程在poll()或者take()的話返回false。
4.3. LinkedBlockingQueue: 這是一個由單鏈表實現的默認×××的阻塞隊列。LinkedBlockingQueue提供了一個可選有界的構造函數,而在未指明容量時,容量默認為Integer.MAX_VALUE。
隊列操作:
5. Executors線程工廠類
1. Executors.newCachedThreadPool();
說明: 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程.
內部實現:new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue<runnable>());</runnable>
2. Executors.newFixedThreadPool(int);
說明: 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。
內部實現:new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<runnable>());</runnable>
3. Executors.newSingleThreadExecutor();
說明:創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照順序執行。
內部實現:new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<runnable>())</runnable>
4. Executors.newScheduledThreadPool(int);
說明:創建一個定長線程池,支持定時及周期性任務執行。
內部實現:new ScheduledThreadPoolExecutor(corePoolSize)
【附】阿里巴巴JAVA開發手冊中對線程池的使用規范
- 【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例:
public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); ... }}
2.【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明: 使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資
源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者
“過度切換”的問題。
3.【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣
的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明: Executors 返回的線程池對象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
2) CachedThreadPool 和 ScheduledThreadPool:
允許的創建線程數量為 Integer.MAX_VALUE, 可能會創建大量的線程,從而導致 OOM。
6. 總結
ThreadPoolExecutor通過幾個核心參數來定義不同類型的線程池,適用于不同的使用場景;其中在任務提交時,會依次判斷corePoolSize, workQueque, 及maximumPoolSize,不同的狀態不同的處理。技術領域水太深,如果不是日常使用,基本一段時間后某些知識點就忘的差不多了,因此階段性地回顧與總結,對夯實自己的技術基礎很有必要。