原文地址:http://www.cnblogs.com/java-zhao/p/5146601.html
1、最基礎的線程池ThreadPoolExecutor
使用方式:
1 /** 2 * ThreadPoolExecutor測試類 3 * 注意: 4 * 1、ThreadPoolExecutor是一個線程池 5 * 2、多個任務都可以由該線程池中選出幾條線程來執行 6 */ 7 public class ThreadPoolExecutorTest { 8 private static ThreadPoolExecutor executor = 9 new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10)); 10 11 public void executeTask(){ 12 Task1 task1 = new Task1();//構建任務1 13 Task2 task2 = new Task2();//構建任務2 14 executor.execute(task1);//執行任務1 15 executor.execute(task2);//執行任務2 16 } 17 18 /* 19 * 基本任務2 20 */ 21 class Task1 implements Runnable{ 22 public void run() { 23 //具體任務的業務 24 for(int i=0;i<1000;i++){ 25 System.out.println("hello xxx!!!"); 26 } 27 } 28 } 29 30 /* 31 * 基本任務2 32 */ 33 class Task2 implements Runnable{ 34 public void run() { 35 //具體任務的業務 36 for(int i=0;i<5;i++){ 37 System.out.println("hello world2!!!"); 38 } 39 } 40 } 41 42 public static void main(String[] args) { 43 ThreadPoolExecutorTest test = new ThreadPoolExecutorTest(); 44 test.executeTask(); 45 } 46 }
說明:
在代碼中,構建了一個線程池(executor)和兩個實現了Runnable接口的任務(task1、task2),並將這兩個任務提交到executor中去執行。
線程池的配置:集合下邊的工作機理與參數詳細說明來說。
當然,上述的執行結果是交叉着的,因爲存在線程的切換。
2、工作機理
A、當一個新的任務被提交到ThreadPoolExecutor的execute()方法中時,如果當前池中正在運行的線程少於corePoolSize,則會創建一個新的線程來處理該任務;
注意:這是池中正在運行的線程,爲什麼這樣說呢?是因爲核心線程是每來一個任務才創建一個線程,這個看第三部分。看完第三部分之後,你就會覺得,其實換個說法:"如果當前池中的線程少於corePoolSize"這樣會更準確,因爲我們也許會通過下邊介紹的方法提前將核心線程創建好,如果假設這時候來了一個任務,而所有的核心線程都處於空閒狀態的話,這時候是不會去創建新線程的。
B、如果當前池中的線程大於等於corePoolSize,但是小於maximumPoolSize時,如果隊列滿了,會創建新的線程來處理任務,如果隊列沒有滿,任務加入到隊列中去;
C、如果隊列滿了,正在運行的線程數已經等於maximumPoolSize時,該任務就會被rejected(回絕)
3、參數詳細說明
A、corePoolSize與maximumPoolSize
- 如果corePoolSize==maximumPoolSize,線程池的size就是固定的了(這一塊兒類似於堆內存的指定,防止擴張帶來的損耗,但要視情況而定);
- 默認情況下,只有當一個新的任務到達時,纔會創建和啓動core threads,但是可以通過prestartCoreThread和prestartAllCoreThreads來改變;
B、ThreadFactory
- 通過使用java.util.concurrent.ThreadFactory可以創建新的線程
- 如果不額外指定ThreadFactory,則使用默認的Executors#defaultThreadFactory;
- 通過該默認的線程工廠,所有創建的線程都會被加入到同一個ThreadGroup中去,並且這些線程都會有相同的優先級(NORM_PRIORITY),並且都是non-daemon線程
注意:這一塊兒有一個後臺(daemon)線程的概念,典型的後臺線程:垃圾回收線程;這個線程與其他應用線程的不同之處在於:當所有的應用線程都沒有後,後臺線程也就自動消失了。
C、keepAliveTime
- 如果pool當前擁有的線程超過了corePoolSize,超出的線程如果在大於keepAliveTime的時間外閒置(idle),這些線程就會被終止;
- 該機制在pool沒有被活躍的使用的時候,可以減少資源浪費;
- 默認情況下,keep-alive機制僅僅會在線程數超過corePoolSizeThreads時纔會被使用;
- 當然,通過使用ThreadPoolExecutor#allowCoreThreadTimeOut(boolean)也可以將這種keep-alive機制應用在core threads上去(只要keepAliveTime>0即可)
D、Queue
任何一種BlockingQueue都可以被用來傳遞和存儲提交到線程池中的任務,有三種隊列策略:
1)SynchronousQueue(默認):
- 直接將任務移交給線程而不是入隊,如果已經沒有線程立即來處理提交到pool中的任務時,會創建一個新的線程來處理該任務;
- 這種策略需要maximumPoolSizes無界來確保新提交的任務不會被rejection;
- 這種方式的最大缺點:當任務到來的速度大於任務被處理的速度時,線程數會瘋長。
2)無界隊列LinkedBlockingQueue:
- 由於隊列無界,當運行的線程等於corePoolSize時,新到來的任務會入隊而不會創建新的線程來執行(即pool中的線程數永遠不會大於corePoolSize);
- 這種方式的缺點:當任務到來的速度大於任務被處理的速度時,隊列長度會瘋長。
3)有界隊列ArrayBlockingQueue:
- 這種方式是非常難處理好的一種方式,要考慮好ArrayBlockingQueue的大小和maximumPoolSize的大小;
- 當ArrayBlockingQueue較大而maximumPoolSize較小時,會降低CPU使用率、減少OS資源、減少上下文切換,但是吞吐量會降低。-->線程較少的特點就是這樣;
- 如果任務頻繁的被阻塞(例如,they are I/O bound),就需要更多的線程了;
- 當ArrayBlockingQueue較小而maximumPoolSize較大時,會使CPU使用繁忙但也會遇到一些不可接受的scheduling,吞吐量也會降低。
說明:這一塊兒配置是一個比較麻煩的地方,後邊會說。
E、回絕任務
執行回絕的場景:看開頭部分的工作機理。
在回絕任務的時候,execute()方法會調用RejectedExecutionHandler#rejectedExecution。有四種handler策略:
1)ThreadPoolExecutor.CallerRunsPolicy:調用execute()的線程自己來處理該任務,絕大部分情況下是主線程。
注意:由於主線程執行這個任務,那麼新到來的任務就不會被提交到線程池中執行(而是提交到TCP層的隊列,TCP層隊列滿了,就開始拒絕,此時性能已經很低了),直到主線程執行完這個任務。
2)ThreadPoolExecutor.DiscardPolicy:不能被執行的任務會直接被扔掉
3)ThreadPoolExecutor.DiscardOldestPolicy:如果executor沒有被關閉,隊列頭部的任務將會被丟棄,然後將該任務加到隊尾
4)ThreadPoolExecutor.AbortPolicy(默認):回絕任務並拋出異常
F、AOP
ThreadPoolExecutor提供了兩個方法在每個任務的執行前後進行調用ThreadPoolExecutor#beforeExecute和ThreadPoolExecutor#afterExecute.
4、開頭實例套用
實例中構建的線程池參數:
- corePoolSize==5
- maximumPoolSize==10
- keepAliveTime==30s
- 隊列:ArrayBlockingQueue,大小爲10
- 線程工廠:defaultThreadFactory(默認)
- 回絕策略:AbortPolicy(默認)
套一下工作機理:
1)當併發提交了<=5個任務到executor中時(此時任務數<=corePoolSize),executor會使用5個核心線程去執行這些任務;
2)當這時候馬上又來了一個任務,如果此時5個核心線程有空閒線程的話,就是用空閒的線程去處理,如果都在忙,這時候該任務進入隊列;
3)之後再來任務,還是像第二步那樣去執行,直到任務將隊列放滿了,這時候,如果再來一個任務,如果5個核心線程有空閒線程,直接去執行該任務,如果5個核心線程都在忙,這時候就創建一個新的線程來執行該任務;
4)如果通過上邊的流程,最後5個線程都在忙,並且隊列滿了,並且pool中的線程數已經是10個了(池中的線程總數==maximumPoolSize了),這時候就要執行回絕策略了,在這裏,使用了默認的AbortPolicy,即直接放棄該任務並拋出異常。
在代碼的執行過程中,如果發現後來創建的5個線程有超過30秒都沒被調用過的,該線程就被回收掉了。
5、線程池生命週期
- 創建之初,狀態爲RUNNNG
- 調用了ExecutorService#shutdown:將之前已經提交上來的任務進行處理(包括隊列中的),但是不再接收新任務(使用回絕策略回絕新任務),狀態SHUNTDOWN
- 調用了ExecutorService#shutdownNow:取消所有運行中的任務(包括隊列中的),並且不再接收新任務(使用回絕策略回絕新任務),狀態STOP/TERMINATED
疑問:(這個疑問我會在看完ThreadPoolExecutor的相關源碼後進行回答)
當隊列滿了之後,這時候來了一個任務,恰好5個核心線程有一個空閒了,那麼下面兩種情況哪一個正確:
1)這個空閒的核心線程直接執行剛剛到來的任務
2)這個空閒的核心線程直接執行隊列頭部的任務,而將剛剛到來的任務放入隊尾
解答:這個問題的答案就一句話,有空閒核心線程,就是用核心線程去執行任務;沒有空閒的核心線程,任務纔會入隊。所以選1)