關於ThreadPool執行緒池的摘記
轉載地址:http://blog.csdn.net/cutesource/article/details/6061229
最近發現幾起對ThreadPoolExecutor的誤用,其中包括自己,發現都是因為沒有仔細看註釋和內部運轉機制,想當然的揣測引數導致,先看一下新建一個ThreadPoolExecutor的構建引數:
- public ThreadPoolExecutor(int corePoolSize,
- int maximumPoolSize,
-
long keepAliveTime,
- TimeUnit unit,
- BlockingQueue<Runnable> workQueue,
- ThreadFactory threadFactory,
- RejectedExecutionHandler handler)
看這個引數很容易讓人以為是執行緒池裡保持corePoolSize個執行緒,如果不夠用,就加執行緒入池直至maximumPoolSize大小,如果還不夠就往workQueue里加,如果workQueue也不夠就用RejectedExecutionHandler來做拒絕處理。
但實際情況不是這樣,具體流程如下:
1)當池子大小小於corePoolSize就新建執行緒,並處理請求
2)當池子大小等於corePoolSize,把請求放入workQueue中,池子裡的空閒執行緒就去從workQueue中取任務並處理
3)當workQueue放不下新入的任務時,新建執行緒入池,並處理請求,如果池子大小撐到了maximumPoolSize就用RejectedExecutionHandler來做拒絕處理
4)另外,當池子的執行緒數大於corePoolSize的時候,多餘的執行緒會等待keepAliveTime長的時間,如果無請求可處理就自行銷燬
內部結構如下所示:
從中可以發現ThreadPoolExecutor就是依靠BlockingQueue的阻塞機制來維持執行緒池,當池子裡的執行緒無事可幹的時候就通過workQueue.take()阻塞住。
其實可以通過Executes來學學幾種特殊的ThreadPoolExecutor是如何構建的。
- publicstatic ExecutorService newFixedThreadPool(int nThreads) {
- returnnew ThreadPoolExecutor(nThreads, nThreads,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>());
- }
newFixedThreadPool就是一個固定大小的ThreadPool
- publicstatic ExecutorService newCachedThreadPool() {
- returnnew ThreadPoolExecutor(0, Integer.MAX_VALUE,
- 60L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
- }
newCachedThreadPool比較適合沒有固定大小並且比較快速就能完成的小任務,沒必要維持一個Pool,這比直接new Thread來處理的好處是能在60秒內重用已建立的執行緒。
其他型別的ThreadPool看看構建引數再結合上面所說的特性就大致知道它的特性
http://victorzhzh.iteye.com/blog/1010359
ExecutorService介面繼承了Executor介面,定義了一些生命週期的方法
Java程式碼- public interface ExecutorService extends Executor {
- void shutdown();
- List<Runnable> shutdownNow();
- boolean isShutdown();
- boolean isTerminated();
- boolean awaitTermination(long timeout, TimeUnit unit)
- throws InterruptedException;
- }
本文,我們逐一分析裡面的每個方法。
首先,我們需要建立一個任務程式碼,這段任務程式碼主要是隨機生成含有10個字元的字串
Java程式碼- /**
- * 隨機生成10個字元的字串
- * @author dream-victor
- *
- */
- public class Task1 implements Callable<String> {
- @Override
- public String call() throws Exception {
- String base = "abcdefghijklmnopqrstuvwxyz0123456789";
- Random random = new Random();
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < 10; i++) {
- int number = random.nextInt(base.length());
- sb.append(base.charAt(number));
- }
- return sb.toString();
- }
- }
然後,我們還需要一個長任務,這裡我們預設是沉睡10秒,
Java程式碼- /**
- * 長時間任務
- *
- * @author dream-victor
- *
- */
- public class LongTask implements Callable<String> {
- @Override
- public String call() throws Exception {
- TimeUnit.SECONDS.sleep(10);
- return "success";
- }
- }
OK,所有前期準備完畢,下面我們就來分析一下ExecutorService介面中和生命週期有關的這些方法:
1、shutdown方法:這個方法會平滑地關閉ExecutorService,當我們呼叫這個方法時,ExecutorService停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另一類是還沒有開始執行的),當所有已經提交的任務執行完畢後將會關閉ExecutorService。這裡我們先不舉例在下面舉例。
2、awaitTermination方法:這個方法有兩個引數,一個是timeout即超時時間,另一個是unit即時間單位。這個方法會使執行緒等待timeout時長,當超過timeout時間後,會監測ExecutorService是否已經關閉,若關閉則返回true,否則返回false。一般情況下會和shutdown方法組合使用。例如:
Java程式碼- ExecutorService service = Executors.newFixedThreadPool(4);
- service.submit(new Task1());
- service.submit(new Task1());
- service.submit(new LongTask());
- service.submit(new Task1());
- service.shutdown();
- while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
- System.out.println("執行緒池沒有關閉");
- }
- System.out.println("執行緒池已經關閉");
這段程式碼中,我們在第三次提交了一個長任務,這個任務將執行10秒沉睡,緊跟著執行了一次shutdown()方法,假設:這時ExecutorService被立即關閉,下面呼叫service.awaitTermination(1, TimeUnit.SECONDS)方法時應該返回true,程式執行結果應該只會打印出:“執行緒池已經關閉”。但是,真實的執行結果如下:
Java程式碼- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池沒有關閉
- 執行緒池已經關閉
這說明我們假設錯誤,service.awaitTermination(1, TimeUnit.SECONDS)每隔一秒監測一次ExecutorService的關閉情況,而長任務正好需要執行10秒,因此會在前9秒監測時ExecutorService為未關閉狀態,而在第10秒時已經關閉,因此第10秒時輸出:執行緒池已經關閉。這也驗證了shutdown方法關閉ExecutorService的條件。
3、shutdownNow方法:這個方法會強制關閉ExecutorService,它將取消所有執行中的任務和在工作佇列中等待的任務,這個方法返回一個List列表,列表中返回的是等待在工作佇列中的任務。例如:
Java程式碼- ExecutorService service = Executors.newFixedThreadPool(3);
- service.submit(new LongTask());
- service.submit(new LongTask());
- service.submit(new LongTask());
- service.submit(new LongTask());
- service.submit(new LongTask());
- List<Runnable> runnables = service.shutdownNow();
- System.out.println(runnables.size());
- while (!service.awaitTermination(1, TimeUnit.MILLISECONDS)) {
- System.out.println("執行緒池沒有關閉");
- }
- System.out.println("執行緒池已經關閉");
這段程式碼中,我們限制了執行緒池的長度是3,提交了5個任務,這樣將有兩個任務在工作佇列中等待,當我們執行shutdownNow方法時,ExecutorService被立刻關閉,所以在service.awaitTermination(1, TimeUnit.MILLISECONDS)方法校驗時返回的是true,因此沒有輸出:執行緒池沒有關閉。而在呼叫shutdownNow方法時,我們接受到了一個List,這裡包含的是在工作佇列中等待執行的任務,由於執行緒池長度為3,且執行的都是長任務,所以當提交了三個任務後執行緒池已經滿了,剩下的兩次提交只能在工作佇列中等待,因此我們看到runnables的大小為2,結果如下:
Java程式碼- 2
- 執行緒池已經關閉
4、isTerminated方法:這個方法會校驗ExecutorService當前的狀態是否為“TERMINATED”即關閉狀態,當為“TERMINATED”時返回true否則返回false。例如:
Java程式碼- ExecutorService service = Executors.newFixedThreadPool(3);
- service.submit(new Task1());
- service.submit(new Task1());
- service.submit(new LongTask());
- service.shutdown();
- System.out.println(System.currentTimeMillis());
- while (!service.isTerminated()) {
- }
- System.out.println(System.currentTimeMillis());
這段程式碼我們執行了兩個正常的任務和一個長任務,然後呼叫了shutdown方法,我們知道呼叫shutdown方法並不會立即關閉ExecutorService,這時我們記錄一下監測迴圈執行前的時間,在沒有關閉前我們一直進入一個空迴圈中,直到 ExecutorService關閉後退出迴圈,這裡我們知道長任務執行時間大約為10秒,我們看一下上述程式執行結果:
Java程式碼- 1303298818621
- 1303298828634
- 相差:10013毫秒,轉換一下除以1000,得到相差大約10秒
這10秒正好是長任務執行的時間,因此在 ExecutorService正常關閉後isTerminated方法返回true。
5、isShutdown方法:這個方法在ExecutorService關閉後返回true,否則返回false。方法比較簡單不再舉例。
以上討論是基於ThreadPoolExecutor的實現,不同的實現會有所不同需注意。