趴一趴線程池那點事

 

 

寫在前面

    多線程的軟件設計,主要是爲了最大程度上利用CPU的使用率最大化生產環境的吞吐量和性能。java

若是對線程管理不當,很是容易形成系統崩潰,線程相對進程而言雖然輕量,無止境的使用更會形成內存泄漏,因此線程使用的 度 須要很好的把控好。數據庫

   作過數據庫連接工做的朋友們,對數據庫鏈接池確定不陌生,用鏈接池來維護一些激活的數據庫連接,須要的時候從鏈接池取,不須要的時候交換給鏈接池而不是真正的銷燬。設計模式

  

RoadMap

     對於線程池的學習,主要分文2個篇幅,線程池的使用和線程池的實現。數組

 

     使用篇:緩存

     開箱即用,JDK對線程池的支持。微信

              建立線程池     多線程

              線程池的使用 併發

              線程池的生命週期框架

              Futre模式與Callable接口函數

   實現篇:

   扒一扒ThreadPoolExecutor              

             找個地方聽任務請求:任務隊列

             超負荷了怎麼辦: reject handler

             

開箱即用,JDK對線程池的支持

   在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點之後就不能進遊客了。 當遊客都走了,賬結算完 超市晚上才正式歇業(終止).

   類比回線程池,那麼線程池關閉,不能提交任務,會將正在處理(不包含隊列中)的任務執行完。 線程所有完成以後 進入終止狀態。

 

Future模式與Callable  

 

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, 這是個接口 根據功能不一樣能夠運用好幾種不一樣的隊列。

 

  • 直接提交隊列:該功能由SynchronousQueue 實現, 是一個特殊的BlockingQueue,由於SynchronousQueue 沒有容量,沒插入一個操做都須要等待對應的刪除操做,反之每個刪除操做都須要等待對應的插入,因此提交的任務是不被保存的,每次都提交給線程,若是沒有線程則建立新的線程,若是到達線程上線則須要執行拒絕策略,一般來講要使用這種Queue 則須要設定很大的MaximumPoolSize,不然可能會拒絕掉不少請求。
  • 有界任務隊列: 該功能由ArrayBlockingQueue實現, 其內部是數組實現因此 初始化的時候須要定義容量,當線程數小於corePoolSize時,會優先建立線程處理隊列的中的任務,若是線程數大於corePoolSize,則存放在隊列,存滿的時候,若是線程數沒有超過maximunPoolSize,則建立線程,不然就須要執行決絕策略。
  • 無界任務隊列: 該功能由LinkedBlockingQueue實現, 由於是鏈表結構 因此能夠實現無界的任務隊列,當線程數小於corePoolSize時,會優先建立線程處理隊列的中的任務,若是線程數等於corePoolSize,不會再建立新的線程,後面的任務會一直添加到任務隊列中,由於沒有界,沒有必要準備拒絕策略來執行,要注意的是,若是添加任務的速度大於處理任務的速度,會出現無限擴容 直到內存耗盡的狀況。
  • 優先任務隊列:該功能由PriorityBlockingQueue實現,它是一個特殊的無界隊列,常規的隊列都是先進先出,PriorityBlockingQueue能夠處理優先級高的任務。

     

超負荷了怎麼辦: 決絕策略。

    前文提到過不少次決絕策略, 這究竟是什麼鬼呢。 其實也很好理解 就是任務隊列滿了以後 須要處理下後續請求,說是說拒絕策略,也未必真的就把人家個拒絕了。

   JDK內置了4種策略

  • AbortPolicy: 中斷策略, 該策略是拋出異常,中斷系統運行
  • CallerRunsPolicy: 不經過線程池的線程來執行任務,容易形成系統資源緊張。有點想
  • DiscardOldestPolicy: 丟棄老請求策略: 若是執行程序還沒有關閉,該策略是會把最老的請求放棄 ,即將要處理的請求,就是把隊列頭的任務放棄掉,新的任務入隊列。
  • DiscardPolicy:丟棄請求策略: 該策略是會放棄沒法處理的請求,其實就是默默丟棄任務的作法。和AbortPolicy相似只是不拋異常

    

 

回看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秒就銷燬。

 

歡迎關注微信公衆號

相關文章
相關標籤/搜索