1. 程式人生 > >關於ThreadPool執行緒池的摘記

關於ThreadPool執行緒池的摘記

轉載地址:http://blog.csdn.net/cutesource/article/details/6061229

最近發現幾起對ThreadPoolExecutor的誤用,其中包括自己,發現都是因為沒有仔細看註釋和內部運轉機制,想當然的揣測引數導致,先看一下新建一個ThreadPoolExecutor的構建引數:

  1. public ThreadPoolExecutor(int corePoolSize,  
  2.                           int maximumPoolSize,  
  3.                           long keepAliveTime,  
  4.                           TimeUnit unit,  
  5.                           BlockingQueue<Runnable> workQueue,  
  6.                           ThreadFactory threadFactory,  
  7.                           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是如何構建的。

  1. publicstatic ExecutorService newFixedThreadPool(int nThreads) {  
  2.     returnnew ThreadPoolExecutor(nThreads, nThreads,  
  3.                                   0L, TimeUnit.MILLISECONDS,  
  4.                                   new LinkedBlockingQueue<Runnable>());  
  5. }  

newFixedThreadPool就是一個固定大小的ThreadPool

  1. publicstatic ExecutorService newCachedThreadPool() {  
  2.     returnnew ThreadPoolExecutor(0, Integer.MAX_VALUE,  
  3.                                   60L, TimeUnit.SECONDS,  
  4.                                   new SynchronousQueue<Runnable>());  
  5. }  

newCachedThreadPool比較適合沒有固定大小並且比較快速就能完成的小任務,沒必要維持一個Pool,這比直接new Thread來處理的好處是能在60秒內重用已建立的執行緒。

其他型別的ThreadPool看看構建引數再結合上面所說的特性就大致知道它的特性


http://victorzhzh.iteye.com/blog/1010359

ExecutorService介面繼承了Executor介面,定義了一些生命週期的方法

Java程式碼  收藏程式碼
  1. public interface ExecutorService extends Executor {  
  2. void shutdown();  
  3. List<Runnable> shutdownNow();  
  4. boolean isShutdown();  
  5. boolean isTerminated();  
  6. boolean awaitTermination(long timeout, TimeUnit unit)  
  7.         throws InterruptedException;  
  8. }  

本文,我們逐一分析裡面的每個方法。

首先,我們需要建立一個任務程式碼,這段任務程式碼主要是隨機生成含有10個字元的字串

Java程式碼  收藏程式碼
  1. /** 
  2.  * 隨機生成10個字元的字串 
  3.  * @author dream-victor 
  4.  * 
  5.  */  
  6. public class Task1 implements Callable<String> {  
  7.     @Override  
  8.     public String call() throws Exception {  
  9.         String base = "abcdefghijklmnopqrstuvwxyz0123456789";  
  10.         Random random = new Random();  
  11.         StringBuffer sb = new StringBuffer();  
  12.         for (int i = 0; i < 10; i++) {  
  13.             int number = random.nextInt(base.length());  
  14.             sb.append(base.charAt(number));  
  15.         }  
  16.         return sb.toString();  
  17.     }  
  18. }  

 然後,我們還需要一個長任務,這裡我們預設是沉睡10秒,

Java程式碼  收藏程式碼
  1. /** 
  2.  * 長時間任務 
  3.  *  
  4.  * @author dream-victor 
  5.  *  
  6.  */  
  7. public class LongTask implements Callable<String> {  
  8.     @Override  
  9.     public String call() throws Exception {  
  10.         TimeUnit.SECONDS.sleep(10);  
  11.         return "success";  
  12.     }  
  13. }  

OK,所有前期準備完畢,下面我們就來分析一下ExecutorService介面中和生命週期有關的這些方法:

1、shutdown方法:這個方法會平滑地關閉ExecutorService,當我們呼叫這個方法時,ExecutorService停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另一類是還沒有開始執行的),當所有已經提交的任務執行完畢後將會關閉ExecutorService。這裡我們先不舉例在下面舉例。

2、awaitTermination方法:這個方法有兩個引數,一個是timeout即超時時間,另一個是unit即時間單位。這個方法會使執行緒等待timeout時長,當超過timeout時間後,會監測ExecutorService是否已經關閉,若關閉則返回true,否則返回false。一般情況下會和shutdown方法組合使用。例如:

Java程式碼  收藏程式碼
  1. ExecutorService service = Executors.newFixedThreadPool(4);  
  2. service.submit(new Task1());  
  3. service.submit(new Task1());  
  4. service.submit(new LongTask());  
  5. service.submit(new Task1());  
  6. service.shutdown();  
  7. while (!service.awaitTermination(1, TimeUnit.SECONDS)) {  
  8.     System.out.println("執行緒池沒有關閉");  
  9. }  
  10. System.out.println("執行緒池已經關閉");  

 這段程式碼中,我們在第三次提交了一個長任務,這個任務將執行10秒沉睡,緊跟著執行了一次shutdown()方法,假設:這時ExecutorService被立即關閉,下面呼叫service.awaitTermination(1, TimeUnit.SECONDS)方法時應該返回true,程式執行結果應該只會打印出:“執行緒池已經關閉”。但是,真實的執行結果如下:

Java程式碼  收藏程式碼
  1. 執行緒池沒有關閉  
  2. 執行緒池沒有關閉  
  3. 執行緒池沒有關閉  
  4. 執行緒池沒有關閉  
  5. 執行緒池沒有關閉  
  6. 執行緒池沒有關閉  
  7. 執行緒池沒有關閉  
  8. 執行緒池沒有關閉  
  9. 執行緒池沒有關閉  
  10. 執行緒池已經關閉  

 這說明我們假設錯誤,service.awaitTermination(1, TimeUnit.SECONDS)每隔一秒監測一次ExecutorService的關閉情況,而長任務正好需要執行10秒,因此會在前9秒監測時ExecutorService為未關閉狀態,而在第10秒時已經關閉,因此第10秒時輸出:執行緒池已經關閉。這也驗證了shutdown方法關閉ExecutorService的條件。

3、shutdownNow方法:這個方法會強制關閉ExecutorService,它將取消所有執行中的任務和在工作佇列中等待的任務,這個方法返回一個List列表,列表中返回的是等待在工作佇列中的任務。例如:

Java程式碼  收藏程式碼
  1. ExecutorService service = Executors.newFixedThreadPool(3);  
  2. service.submit(new LongTask());  
  3. service.submit(new LongTask());  
  4. service.submit(new LongTask());  
  5. service.submit(new LongTask());  
  6. service.submit(new LongTask());  
  7. List<Runnable> runnables = service.shutdownNow();  
  8. System.out.println(runnables.size());  
  9. while (!service.awaitTermination(1, TimeUnit.MILLISECONDS)) {  
  10.     System.out.println("執行緒池沒有關閉");  
  11. }  
  12. System.out.println("執行緒池已經關閉");  

 這段程式碼中,我們限制了執行緒池的長度是3,提交了5個任務,這樣將有兩個任務在工作佇列中等待,當我們執行shutdownNow方法時,ExecutorService被立刻關閉,所以在service.awaitTermination(1, TimeUnit.MILLISECONDS)方法校驗時返回的是true,因此沒有輸出:執行緒池沒有關閉。而在呼叫shutdownNow方法時,我們接受到了一個List,這裡包含的是在工作佇列中等待執行的任務,由於執行緒池長度為3,且執行的都是長任務,所以當提交了三個任務後執行緒池已經滿了,剩下的兩次提交只能在工作佇列中等待,因此我們看到runnables的大小為2,結果如下:

Java程式碼  收藏程式碼
  1. 2  
  2. 執行緒池已經關閉  

4、isTerminated方法:這個方法會校驗ExecutorService當前的狀態是否為“TERMINATED”即關閉狀態,當為“TERMINATED”時返回true否則返回false。例如:

Java程式碼  收藏程式碼
  1. ExecutorService service = Executors.newFixedThreadPool(3);  
  2. service.submit(new Task1());  
  3. service.submit(new Task1());  
  4. service.submit(new LongTask());  
  5. service.shutdown();  
  6. System.out.println(System.currentTimeMillis());  
  7. while (!service.isTerminated()) {  
  8. }  
  9. System.out.println(System.currentTimeMillis());  

這段程式碼我們執行了兩個正常的任務和一個長任務,然後呼叫了shutdown方法,我們知道呼叫shutdown方法並不會立即關閉ExecutorService,這時我們記錄一下監測迴圈執行前的時間,在沒有關閉前我們一直進入一個空迴圈中,直到 ExecutorService關閉後退出迴圈,這裡我們知道長任務執行時間大約為10秒,我們看一下上述程式執行結果:

Java程式碼  收藏程式碼
  1. 1303298818621  
  2. 1303298828634  
  3. 相差:10013毫秒,轉換一下除以1000,得到相差大約10秒  

這10秒正好是長任務執行的時間,因此在 ExecutorService正常關閉後isTerminated方法返回true。

5、isShutdown方法:這個方法在ExecutorService關閉後返回true,否則返回false。方法比較簡單不再舉例。

以上討論是基於ThreadPoolExecutor的實現,不同的實現會有所不同需注意。