1、什么是線程池
java.util.concurrent.Executors提供了一個 java.util.concurrent.Executor接口的實現用於創建線程池
多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。
假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。
如果:T1 + T3 遠大於 T2,則可以采用線程池,以提高服務器性能。
一個線程池包括以下四個基本組成部分:
1、線程池管理器(ThreadPool):用於創建並管理線程池,包括 創建線程池,銷毀線程池,添加新任務;
2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;
3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完后的收尾工作,任務的執行狀態等;
4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩沖機制。
線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。
線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:
假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。
2.常見線程池
①newSingleThreadExecutor
單個線程的線程池,即線程池中每次只有一個線程工作,單線程串行執行任務
②newFixedThreadExecutor(n)
固定數量的線程池,沒提交一個任務就是一個線程,直到達到線程池的最大數量,然后后面進入等待隊列直到前面的任務完成才繼續執行
③newCacheThreadExecutor(推薦使用)
可緩存線程池,當線程池大小超過了處理任務所需的線程,那么就會回收部分空閑(一般是60秒無執行)的線程,當有任務來時,又智能的添加新線程來執行。
④newScheduleThreadExecutor
大小無限制的線程池,支持定時和周期性的執行線程
java提供的線程池更加強大,相信理解線程池的工作原理,看類庫中的線程池就不會感到陌生了。
要配置一個線程池是比較復雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在Executors類里面提供了一些靜態工廠,生成一些常用的線程池。
2.1 newSingleThreadExecutor
創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
2.2 newFixedThreadPool
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。
2.3 newCachedThreadPool
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,
那么就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
2.4 newScheduledThreadPool
創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。
3 為什么不建議使用 Executors靜態工廠構建線程池
阿里巴巴Java開發手冊,明確指出不允許使用Executors靜態工廠構建線程池
原因如下:
線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險
說明:Executors返回的線程池對象的弊端如下:
1:FixedThreadPool 和 SingleThreadPool:
允許的請求隊列(底層實現是LinkedBlockingQueue)長度為Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM
2:CachedThreadPool 和 ScheduledThreadPool
允許的創建線程數量為Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
創建線程池的正確姿勢
避免使用Executors創建線程池,主要是避免使用其中的默認實現,那么我們可以自己直接調用ThreadPoolExecutor的構造函數來自己創建線程池。在創建的同時,給BlockQueue指定容量就可以了。
private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue(10));
或者是使用開源類庫:開源類庫,如apache和guava等。
3、線程池常用參數
1 /** 2 * Creates a new {@code ThreadPoolExecutor} with the given initial 3 * parameters. 4 * 5 * @param corePoolSize the number of threads to keep in the pool, even 6 * if they are idle, unless {@code allowCoreThreadTimeOut} is set 7 * @param maximumPoolSize the maximum number of threads to allow in the 8 * pool 9 * @param keepAliveTime when the number of threads is greater than 10 * the core, this is the maximum time that excess idle threads 11 * will wait for new tasks before terminating. 12 * @param unit the time unit for the {@code keepAliveTime} argument 13 * @param workQueue the queue to use for holding tasks before they are 14 * executed. This queue will hold only the {@code Runnable} 15 * tasks submitted by the {@code execute} method. 16 * @param threadFactory the factory to use when the executor 17 * creates a new thread 18 * @param handler the handler to use when execution is blocked 19 * because the thread bounds and queue capacities are reached 20 * @throws IllegalArgumentException if one of the following holds:<br> 21 * {@code corePoolSize < 0}<br> 22 * {@code keepAliveTime < 0}<br> 23 * {@code maximumPoolSize <= 0}<br> 24 * {@code maximumPoolSize < corePoolSize} 25 * @throws NullPointerException if {@code workQueue} 26 * or {@code threadFactory} or {@code handler} is null 27 */ 28 29 public ThreadPoolExecutor(int corePoolSize, 30 int maximumPoolSize, 31 long keepAliveTime, 32 TimeUnit unit, 33 BlockingQueue<Runnable> workQueue, 34 ThreadFactory threadFactory, 35 RejectedExecutionHandler handler) { }
corePoolSize:核心線程數量,會一直存在,除非allowCoreThreadTimeOut設置為true
maximumPoolSize:線程池允許的最大線程池數量
keepAliveTime:線程數量超過corePoolSize,空閑線程的最大超時時間
unit:超時時間的單位
workQueue:工作隊列,保存未執行的Runnable 任務
threadFactory:創建線程的工廠類
handler:當線程已滿,工作隊列也滿了的時候,會被調用。被用來實現各種拒絕策略。
從源碼中可以看出,線程池的構造函數有7個參數,分別是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。下面會對這7個參數一一解釋。
一、corePoolSize 線程池核心線程大小
線程池中會維護一個最小的線程數量,即使這些線程處理空閑狀態,他們也不會被銷毀,除非設置了allowCoreThreadTimeOut。這里的最小線程數量即是corePoolSize。
二、maximumPoolSize 線程池最大線程數量
一個任務被提交到線程池以后,首先會找有沒有空閑存活線程,如果有則直接將任務交給這個空閑線程來執行,如果沒有則會緩存到工作隊列(后面會介紹)中,如果工作隊列滿了,才會創建一個新線程,然后從工作隊列的頭部取出一個任務交由新線程來處理,而將剛提交的任務放入工作隊列尾部。線程池不會無限制的去創建新線程,它會有一個最大線程數量的限制,這個數量即由maximunPoolSize指定。
三、keepAliveTime 空閑線程存活時間
一個線程如果處於空閑狀態,並且當前的線程數量大於corePoolSize,那么在指定時間后,這個空閑線程會被銷毀,這里的指定時間由keepAliveTime來設定
四、unit 空閑線程存活時間單位
keepAliveTime的計量單位
五、workQueue 工作隊列
新任務被提交后,會先進入到此工作隊列中,任務調度時再從隊列中取出任務。jdk中提供了四種工作隊列:
①ArrayBlockingQueue
基於數組的有界阻塞隊列,按FIFO排序。新任務進來后,會放到該隊列的隊尾,有界的數組可以防止資源耗盡問題。當線程池中線程數量達到corePoolSize后,再有新任務進來,則會將任務放入該隊列的隊尾,等待被調度。如果隊列已經是滿的,則創建一個新線程,如果線程數量已經達到maxPoolSize,則會執行拒絕策略。
②LinkedBlockingQuene
基於鏈表的無界阻塞隊列(其實最大容量為Interger.MAX),按照FIFO排序。由於該隊列的近似無界性,當線程池中線程數量達到corePoolSize后,再有新任務進來,會一直存入該隊列,而不會去創建新線程直到maxPoolSize,因此使用該工作隊列時,參數maxPoolSize其實是不起作用的。
③SynchronousQuene
一個不緩存任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,不會緩存,而是直接被調度執行該任務,如果沒有可用線程,則創建新線程,如果線程數量達到maxPoolSize,則執行拒絕策略。
④PriorityBlockingQueue
具有優先級的無界阻塞隊列,優先級通過參數Comparator實現。
六、threadFactory 線程工廠
創建一個新線程時使用的工廠,可以用來設定線程名、是否為daemon線程等等
七、handler 拒絕策略
當工作隊列中的任務已到達最大限制,並且線程池中的線程數量也達到最大限制,這時如果有新任務提交進來,該如何處理呢。這里的拒絕策略,就是解決這個問題的,jdk中提供了4中拒絕策略:
①CallerRunsPolicy
該策略下,在調用者線程中直接執行被拒絕任務的run方法,除非線程池已經shutdown,則直接拋棄任務。
②AbortPolicy
該策略下,直接丟棄任務,並拋出RejectedExecutionException異常。
③DiscardPolicy
該策略下,直接丟棄任務,什么都不做。
④DiscardOldestPolicy
該策略下,拋棄進入隊列最早的那個任務,然后嘗試把這次拒絕的任務放入隊列
到此,構造線程池時的七個參數,就全部介紹完畢了。