1. 程式人生 > >12.深入線程池_流程和原理

12.深入線程池_流程和原理

thread 帶來 discard 分享 取消 fixed 由於 ref epo

參考文:http://blog.csdn.net/mark_lq/article/details/50346999

一、線程池的基本類結構

  合理利用線程池能夠帶來三個好處。

  1.降低資源消耗。過重復利用已創建的線程降低線程創建和銷毀造成的消耗

  2.提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行

  3.提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控

  Executor線程池框架最大優點是把任務的提交和執行解耦。呵護短將要執行的任務封裝成Task,然後提交即可。具體來說,提交一個Callable對象給ExecutorService(如最常用的線程池ThreadPoolExecutor),將得到一個Future對象,調用Future對象的get方法等待執行結果

  下圖是線程池所涉及到的所有類的結構圖,先從整體把握下

技術分享
??????????????圖1 線程池實現原理類結構圖

??上面這個圖是很復雜的,涉及到了線程池內部實現原理的所有類,不利於我們理解線程池如何使用。我們先從客戶端的角度出發,看看客戶端使用線程池所涉及到的類結構圖:
技術分享
??????????????圖2 線程池使用的基本類結構圖

??從圖一可知,實際的線程池類是實現ExecutorService接口的類,有ThreadPoolExecutor、ForkJoinPool和ScheduledThreadPoolExecutor。下面以常用的ThreadPoolExecutor為例講解。

二、線程池的實現步驟

  a)線程池的創建

技術分享

1 public ThreadPoolExecutor(int corePoolSize,
2                           int maximumPoolSize,
3                           long keepAliveTime,
4                           TimeUnit unit,
5                           BlockingQueue<Runnable> workQueue,
6                           ThreadFactory threadFactory,
7 RejectedExecutionHandler handler)

註:當我們創建一個線程池的時候,並不會直接就創建出相應數量的線程

而是,只有當提交一個任務到線程池時,在當前線程數小於線程池的基本線程數數線程池時,會創建一個線程來執行任務,即使其他空閑的基本線程能夠執行新任務也會創建線程

如果調用了線程池的 prestarAllCoreThreads方法,線程池會提前創建並啟動所有基本線程

參數說明

  1.corePoolSize (線程池的基本線程數)如:Executors.newFixedThreadPool(5),它的基本線程數就是5

  2.maxinumPoolSize (線程池最大線程數)線程池允許創建的最大線程數。如果任務隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得註意的是如果使用了無界的任務隊列這個參數就沒什麽效果

  註:一般默認創建線程池的的時候,maxinumPoolSize = corePoolSize ,即任務隊列滿了的話,就直接拒絕了,不會創建線程

  關於任務隊列,我們後面會再詳解介紹

  3.keepAliveTime(線程活動保持時間) 這個參數表示,線程池的工作線程在空閑狀態下,存活的時間。(默認為0,即線程閑下來就將其釋放)所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率

  4.TimeUnit (線程活動保持時間的單位),這個參數是為前面那個參數服務的,默認為 毫秒

  

  5.workQueue(任務隊列) 用於保存等待執行的任務的阻塞隊列,當線程池中線程執行任務執行不過來的時候,會將等待執行的任務放到這個隊列中,可以選擇以下幾個阻塞隊列

  • ArrayBlockingQueue:是一個基於數組的有界阻塞隊列,此隊列按FIFO原則對元素進行排序

  • LinkBlockingQueue:一個基於鏈表結構的無界阻塞隊列,此隊列按FIFO排序元素,吞吐量要高於ArrayBlockingQueue,靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
  JDK中默認選用LinkedBlockingQueue作為阻塞隊列的原因就在於其無界性。因為線程大小固定的線程池,其線程的數量是不具備伸縮性的,當任務非常繁忙的時候,就勢必會導致所有的線程都處於工作狀態,如果使用一個有界的阻塞隊列來進行處理,那麽就非常有可能很快導致隊列滿的情況發生,從而導致任務無法提交而拋出RejectedExecutionException,而使用無界隊列由於其良好的存儲容量的伸縮性,可以很好的去緩沖任務繁忙情況下場景,即使任務非常多,也可以進行動態擴容,當任務被處理完成之後,隊列中的節點也會被隨之被GC回收,非常靈活。
  
  • SynchronousQueue:一個不存儲元素的無界阻塞隊列,每個插入操作必須要等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態。即隊列中添加任務後,必須要有線程來取走這個任務。它將任務直接提交給線程而不保持它們,如果不存在可用於立即運行任務的線程,則會構造出一個新的線程
  所以Executors.newCachedThreadPool使用了這個隊列,因此它是一個緩存線程池,線程池的數量不固定,可以根據需求自動的更改數量,它的吞吐量吞吐量通常要高於LinkedBlockingQueue   6.ThreadFactory:用於設置創建線程的工廠,通過線程工廠給每個創建出來的線程設置更有意義的名字

  7.RejectExecutionHandler(拒絕策略):當隊列和線程池都滿了,什麽時候會出現這種情況呢?應該是要滿足: (任務隊列選用的是有界的隊列,任務隊列滿已時,且當前線程數也已經達到了線程池的最大線程數maxinumPoolSize

  那麽就必須要采取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出的異常。以下是JDK1.5提供的四種策略

技術分享

    AbortPolicy:直接拋出異常

    CallerRunsPolicy:只用調用者所在線程來運行任務

    DiscardOldestPolicy:丟棄隊列裏最後一個要執行任務,並執行當前任務

    DiscardPolicy:不處理,直接丟棄

  當然也可以根據應用場景來實現RejectedExecutionHandler接口自定義拒絕策略,如記錄到日誌或持久化不能處理的任務

  

  由此可見,創建一個線程所需的參數非常多,線程池為我們提供了類Executors的靜態工廠方法用來創建不用類型的線程池,官方也建議我們使用它,它會給上面的參數給上一些默認值

  技術分享

  當然我們也可以自己創建線程池,自由地給定參數,來更好的適應不同的場景

  b)向線程池提交任務

  有兩種方式提交任務(execute 和 submit)兩者執行任務最後都會通過Executor的execute方法來執行,關於 execute方法,我們後面會詳解,

  兩者區別:1.異常處理,兩者對待run方法拋出的異常處理方式不一樣

        2.有無返回值,submit有返回值,而execute沒有

  具體怎麽用,可以參考上一篇文章,

  c)線程池關閉

  1.shutdown()方法

    這個方法會平滑地關閉ExecutorService,當我們調用這個方法時,ExecutorService停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務分兩類:一類是已經在執行的,另一類是沒有開始執行的),當所有已經提交的任務執行完畢後將會關閉 ExecutorService

  2.awaitTermination(long timeout,TimeUnit unit)方法

    這個方法有兩個參數,一個是timeout即超時時間,另一個是unit即時間單位。這個方法會使當前關閉線程池的線程 等待 timeout時長,當超過timeout時間後,則去監測ExecutorService是否已經關閉,若關閉則返回true,否則返回false。一般情況下會和shutdown方法組合使用

  3.shutdownNow()方法:這個方法會強制關閉ExecutorService,它將取消所有運行中的任務和在工作隊列中等待的任務,這個方法返回一個List列表,列表中返回的是等待在工作隊列中任務

三、線程池的執行流程分析

  前面提到ExecutorService的submit方法 和 execute方法都會調用Executor實現類(如ThreadPoolExecutor)的execute方法,下面我們來看看任務提交到這個方法是如何執行的,從這個方法入手分析 線程池的執行流程

源碼 谷歌翻譯

12.深入線程池_流程和原理