12.深入線程池_流程和原理
參考博文: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:一個不存儲元素的無界阻塞隊列,每個插入操作必須要等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態。即隊列中添加任務後,必須要有線程來取走這個任務。它將任務直接提交給線程而不保持它們,如果不存在可用於立即運行任務的線程,則會構造出一個新的線程
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.深入線程池_流程和原理