线程池实战
一、線程池的背景
Java線程的創建非常昂貴,需要JVM和OS(操作系統)配合完成大量的工作:
(1)必須為線程堆棧分配和初始化大量內存塊,其中包含至少1MB的棧內存。
(2)需要進行系統調用,以便在OS(操作系統)中創建和注冊本地線程。Java高并發應用頻繁創建和銷毀線程的操作是非常低效的,而且是不被編程規范所允許的。如何降低Java線程的創建成本?必須使用到線程池。
線程池主要解決了以下兩個問題:
(1)提升性能:線程池能獨立負責線程的創建、維護和分配。在執行大量異步任務時,可以不需要自己創建線程,而是將任務交給線程池去調度。線程池能盡可能使用空閑的線程去執行異步任務,最大限度地對已經創建的線程進行復用,使得性能提升明顯。
(2)線程管理:每個Java線程池會保持一些基本的線程統計信息,例如完成的任務數量、空閑時間等,以便對線程進行有效管理,使得能對所接收到的異步任務進行高效調度。
二、JUC(java.util.concurrent)的線程池架構
線程池的創建方式
無論在線程池上面做多少封裝,都是要最終調用到下面的構造方法
/*** Creates a new {@code ThreadPoolExecutor} with the given initial* parameters.** @param corePoolSize the number of threads to keep in the pool, even* if they are idle, unless {@code allowCoreThreadTimeOut} is set* @param maximumPoolSize the maximum number of threads to allow in the* pool* @param keepAliveTime when the number of threads is greater than* the core, this is the maximum time that excess idle threads* will wait for new tasks before terminating.* @param unit the time unit for the {@code keepAliveTime} argument* @param workQueue the queue to use for holding tasks before they are* executed. This queue will hold only the {@code Runnable}* tasks submitted by the {@code execute} method.* @param threadFactory the factory to use when the executor* creates a new thread* @param handler the handler to use when execution is blocked* because the thread bounds and queue capacities are reached* @throws IllegalArgumentException if one of the following holds:<br>* {@code corePoolSize < 0}<br>* {@code keepAliveTime < 0}<br>* {@code maximumPoolSize <= 0}<br>* {@code maximumPoolSize < corePoolSize}* @throws NullPointerException if {@code workQueue}* or {@code threadFactory} or {@code handler} is null*/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}線程創建調度流程
線程池的隊列
?Java中的阻塞隊列(BlockingQueue)與普通隊列相比有一個重要的特點:在阻塞隊列為空時會阻塞當前線程的元素獲取操作。具體來說,在一個線程從一個空的阻塞隊列中獲取元素時線程會被阻塞,直到阻塞隊列中有了元素;當隊列中有元素后,被阻塞的線程會自動被喚醒(喚醒過程不需要用戶程序干預)。Java線程池使用BlockingQueue實例暫時接收到的異步任務,BlockingQueue是JUC包的一個超級接口,比較常用的實現類有:
(1)ArrayBlockingQueue:是一個數組實現的有界阻塞隊列(有界隊列),隊列中的元素按FIFO排序。ArrayBlockingQueue在創建時必須設置大小,接收的任務超出corePoolSize數量時,任務被緩存到該阻塞隊列中,任務緩存的數量只能為創建時設置的大小,若該阻塞隊列已滿,則會為新的任務創建線程,直到線程池中的線程總數大于maximumPoolSize。
(2)LinkedBlockingQueue:是一個基于鏈表實現的阻塞隊列,按FIFO排序任務,可以設置容量(有界隊列),不設置容量則默認使用Integer.Max_VALUE作為容量(無界隊列)。該隊列的吞吐量高于ArrayBlockingQueue。如果不設置LinkedBlockingQueue的容量(無界隊列),當接收的任務數量超出corePoolSize時,則新任務可以被無限制地緩存到該阻塞隊列中,直到資源耗盡。有兩個快捷創建線程池的工廠方法Executors.newSingleThreadExecutor和Executors.newFixedThreadPool使用了這個隊列,并且都沒有設置容量(無界隊列)。
(3)PriorityBlockingQueue:是具有優先級的無界隊列。
(4)DelayQueue:這是一個無界阻塞延遲隊列,底層基于PriorityBlockingQueue實現,隊列中每個元素都有過期時間,當從隊列獲取元素(元素出隊)時,只有已經過期的元素才會出隊,隊列頭部的元素是過期最快的元素。快捷工廠方法Executors.newScheduledThreadPool所創建的線程池使用此隊列。
(5)SynchronousQueue:(同步隊列)是一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程的調用移除操作,否則插入操作一直處于阻塞狀態,其吞吐量通常高于LinkedBlockingQueue。快捷工廠方法Executors.newCachedThreadPool所創建的線程池使用此隊列。與前面的隊列相比,這個隊列比較特殊,它不會保存提交的任務,而是直接新建一個線程來執行新來的任務
線程池拒絕策略
在線程池的任務緩存隊列為有界隊列(有容量限制的隊列)的時候,如果隊列滿了,提交任務到線程池的時候就會被拒絕。總體來說,任務被拒絕有兩種情況:(1)線程池已經被關閉。(2)工作隊列已滿且maximumPoolSize已滿。
(1)AbortPolicy使用該策略時,如果線程池隊列滿了,新任務就會被拒絕,并且拋出RejectedExecutionException異常。該策略是線程池默認的拒絕策略。
(2)DiscardPolicy該策略是AbortPolicy的Silent(安靜)版本,如果線程池隊列滿了,新任務就會直接被丟掉,并且不會有任何異常拋出。
(3)DiscardOldestPolicy拋棄最老任務策略,也就是說如果隊列滿了,就會將最早進入隊列的任務拋棄,從隊列中騰出空間,再嘗試加入隊列。因為隊列是隊尾進隊頭出,隊頭元素是最老的,所以每次都是移除隊頭元素后再嘗試入隊。
(4)CallerRunsPolicy調用者執行策略。在新任務被添加到線程池時,如果添加失敗,那么提交任務線程會自己去執行該任務,不會使用線程池中的線程去執行新任務。在以上4種內置策略中,線程池默認的拒絕策略為AbortPolicy,如果提交的任務被拒絕,線程池就會拋出RejectedExecutionException異常,該異常是非受檢異常(運行時異常),很容易忘記捕獲。如果關心任務被拒絕的事件,需要在提交任務時捕獲RejectedExecutionException異常。
(5)自定義策略如果以上拒絕策略都不符合需求,那么可自定義一個拒絕策略,實現RejectedExecutionHandler接口的rejectedExecution方法即可。
線程池狀態轉換
確定線程池的線程數
(1)由于IO密集型任務的CPU使用率較低,導致線程空余時間很多,因此通常需要開CPU核心數兩倍的線程。當IO線程空閑時,可以啟用其他線程繼續使用CPU,以提高CPU的使用率。
(2)CPU密集型任務也叫計算密集型任務,其特點是要進行大量計算而需要消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等。CPU密集型任務雖然也可以并行完成,但是并行的任務越多,花在任務切換的時間就越多,CPU執行任務的效率就越低,所以要最高效地利用CPU,CPU密集型任務并行執行的數量應當等于CPU的核心數。
(3)混合型任務
最佳線程數目=(線程等待時間與線程cpu時間之比+1)*cpu核數
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
- 上一篇: 线程的基本操作
- 下一篇: JAVA线程之间的通信