多線程的軟件設計,主要是爲了最大程度上利用CPU的使用率最大化生產環境的吞吐量和性能。java
若是對線程管理不當,很是容易形成系統崩潰,線程相對進程而言雖然輕量,無止境的使用更會形成內存泄漏,因此線程使用的 度 須要很好的把控好。數據庫
作過數據庫連接工做的朋友們,對數據庫鏈接池確定不陌生,用鏈接池來維護一些激活的數據庫連接,須要的時候從鏈接池取,不須要的時候交換給鏈接池而不是真正的銷燬。設計模式
對於線程池的學習,主要分文2個篇幅,線程池的使用和線程池的實現。數組
使用篇:緩存
開箱即用,JDK對線程池的支持。微信
建立線程池 多線程
線程池的使用 併發
線程池的生命週期框架
Futre模式與Callable接口函數
實現篇:
扒一扒ThreadPoolExecutor
找個地方聽任務請求:任務隊列
超負荷了怎麼辦: reject handler
在JDK5.0 引入 Current包以後 提供了對線程池的支持(Executor 框架). 看一下它家的族譜:
Executor 和ExecutorService 是接口,AbstractExecutorService 是抽象類,實現了公共方法, 它的子類分別指向了不一樣類型的Executor, 今天咱們要講的是這個 ThreadPoolExecutor,線程池。
Executors 是一個工廠類(別和Executor 接口搞混了哈, 雖然只差了 一個s),它是用來建立各式各樣的Executor,其中包括了線程池, 定時調度的任務池等等。
用Executors 建立線程池 很是簡單,調用對應的方法便可,傳遞的參數 是 線程數量 或者 線程建立工廠,須要自行實現建立線程的方法。
newCachedThreadPool() newCachedThreadPool(ThreadFactory factory) |
緩存線程 的線程池 | 當任務提交過來,若是沒有空閒的線程,則建立新線程,來執行任務。 注意: 若是提交任務的速度大於 完成任務的速度,那麼會不斷的有新線程建立,直到內存耗盡。 因此在使用這類線程池的時候要加倍注意。 |
newSingleThreadPool(ThreadFactory factory) | 單線程的線程池 | |
newFixedThreadPool(int nThreads) newFixedThreadPool(int nThreads, ThreadFactory factory) |
固定線程數量的線程池 | 當n =1 的時候 與SingleThreadPool 做用相似 |
void shutdown() | 再也不接受新的任務, 待現有的任務完成後關閉線程 |
List<Runnable> shutDownNow() | 嘗試中止 正在執行的線程任務,並關閉線程池 返回的是提交給線程池,還沒執行的線程組。 |
boolean isShutdown() | 判斷線程池是否關閉 |
boolean isTerminated() | 判斷線程池是否終止 Shutdown 和 terminated 的區別下文【線程池的生命週期】提到,這與ExecutorService 的生命週期有關。 |
boolean awaitTermination(long timeout, TimeUnit unit) | 接收人timeout和TimeUnit兩個參數,阻塞當前線程一段時間來檢測線程池的終止狀態。 若是終止返回 true。 如下三種狀況會結束這種阻塞。 1. 超過設定的等待時間。 2. 等待的這段時間,線程池中的任務所有完成進入 終止狀態。 3. 或者當前線程遇到interrupttion |
<T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); void execute(Runnable command); |
提交任務到當前Executor 的任務隊列,等待調度完成。 這幾個方法的區別在下文【Future模式與Callable】會提到關與名詞Future和新接口Callable |
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) |
提交這個集合裏面全部的任務,等集合任務全部任務完成 纔算完成,這個timeout 是隻整個集合的超時時間,而不是單個 |
<T> T invokeAny(Collection<? extends Callable<T>> tasks) <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) |
提交這個集合裏面全部的任務,等集合任務任意一個任務完成 纔算完成,這個timeout 是隻整個的超時時間 |
線程池,簡單能夠分爲三個階段, 運行階段, 關閉階段,終止階段。
運行階段沒什麼好說的, 主要是區分一下關閉階段和終止階段。
打個比方可能會好理解一點,把線程池比作超市,遊客比作線程,超市通常是晚上10點中止營業,10點之後就是關閉狀態,試下一下,若是10之後你還在超市裏面逛,超市並不會把殺死對嗎, 只是催着你趕忙去結帳對嗎, 另外超市晚上10點之後就不能進遊客了。 當遊客都走了,賬結算完 超市晚上才正式歇業(終止).
類比回線程池,那麼線程池關閉,不能提交任務,會將正在處理(不包含隊列中)的任務執行完。 線程所有完成以後 進入終止狀態。
Callable 是在java.util.concurrent 包中新接口,若是要實現線程, 實現Runnable 和Callable 接口均可以,
區別在於, runnable 沒有返回值,不拋出異常,one way的方式,因此使用runnable的時候,異常基本上都是線程內部處理,不可以交給主線程來處理,
甚至有實現,這個線程掛了或有沒有執行完都不知道。
Callable 擺脫了前面說的這種狀況,它加了範性的返回值,同時容許拋出異常,這樣對多線程的調度和處理更加靈活一些。
Future是JDK內置的併發設計模式中的Future模式,有興趣的同窗能夠做爲擴展閱讀深刻了解一下,這個經典模式。
這裏簡單的介紹一下:
通常狀況,若是要拿到方法的返回值 是否是要等方法運行完,若是這個方法要運行好久,那豈不是要等好久。
那開子線程不是不用等了嗎?非也,線程在計算完以前,可能這個結果是個null,對你後面的邏輯也是有影響的。
因此就引入了FutureData的概念,子線程計算的結果 對我來講是一個FutureData, 我只是須要知道 它計算完畢以後我去取一下數據就能夠了。
或者你能夠這麼認爲,這個Future對象是一箇中介,它持有對線程計算結果的引用同時也有線程計算完成的狀態,你以後問一下中間人,計算完了嗎?
完了 那麼把結果給我。
扒一扒ThreadPoolExecutor
用Executors建立線程池的時候 經過IDE點進那個方法進去 你會發現,哇塞 都是調用了同一個類的構造方法。That is ThreadPoolExecutor.
ThreadPoolExecutor 的構造方法是被重載的,整體歸納起來,須要設定這麼幾個參數
corePoolSize : 指定線程池 常駐線程的數量,
maximumPoolSize: 指定線程池 最大的線程數量
keepAliveTime: 容許線程最大空閒時間, 若是當線程數大於corePoolSize的時候 會釋放空閒的線程來節約內存。
TimeUnit: 空閒時間的單位
BlockingQueue<Runnable> 任務隊列, 提交到線程池的任務就是放在這的。
ThreadFactory 線程建立工廠用來建立線程
RefectedExecutionHandler : 主要用於 當須要處理 任務隊列滿了以後 拒絕提交狀況下的處理。
邏輯圖:
這裏須要具體說說 這個任務隊列,構造函數裏面的聲明的是,BlockingQueue, 這是個接口 根據功能不一樣能夠運用好幾種不一樣的隊列。
前文提到過不少次決絕策略, 這究竟是什麼鬼呢。 其實也很好理解 就是任務隊列滿了以後 須要處理下後續請求,說是說拒絕策略,也未必真的就把人家個拒絕了。
JDK內置了4種策略
回看Executors創建的幾個線程池:
Executors在建線程池的時候,就是在這個幾個參數的上作選擇,以實現不一樣類型的線程池
WorkQueue | corePoolSize | MaximumPoolSize | aliveTime | Reject Handler | |
singleThreadPool | LinkedBlockingQueue | 1 | 1 | 0 second | AbortPolicy |
FixedThreadPool |
LinkedBlockingQueue | 定義的線程數 | 定義的線程數 | 0 second | AbortPolicy |
cachedThreadPool | SynchronousQueue | 0 | Integer.MAX_VALUE | 60 second | AbortPolicy |
JDK 默認的Policy 是AbortPolicy ,儘管沒有傳遞這個參數,JDK 默認採用終止策略,
有意思的是,cachedThreadPool 用的是SynchronousQueue,即來了請求就開線程,線程空閒60秒就銷燬。
歡迎關注微信公衆號