配置ThreadPoolExecutor(java併發編程第8章)

Executors的靜態方法newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool所返回的線程池都是ThreadPoolExecutor對象或者其子類對象. ThreadPoolExecutor提供了多種配置, 可以根據實際定製合適的線程池.

 

線程的創建和銷燬

ThreadPoolExecutor構造函數中的corePoolSize, maximumPoolSize, keepAliveTime參數與線程的創建和銷燬相關. 

corePoolSize指定ThreadPoolExecutor中持有的核心線程數, 除非task隊列已滿, ThreadPoolExecutor不會創建超過核心線程數的線程(corePoolSize爲0時是一種特殊情況, 此時就算task隊列沒有飽和, 向線程池第一次提交task時仍然會創建新的線程), 核心線程一旦創建就不會銷燬, 除非設置了allowCoreThreadTimeOut(true), 或者關閉線程池.

maximumPoolSize指定線程池中持有的最大線程數. 對於超過核心線程數的線程, 如果在指定的超時時間內沒有使用到, 就會被銷燬.

keepAliveTime指定超時時間.

Executors類的靜態方法創建線程池的源碼:

Java代碼  收藏代碼
  1. public static ExecutorService newCachedThreadPool() {  
  2.     // 核心線程數爲0, 最大線程數爲Integer.MAX_VALUE, 超時時間爲60s  
  3.     return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());  
  4. }  
  5.   
  6. public static ExecutorService newFixedThreadPool(int nThreads) {  
  7.     // 核心線程數和最大線程數都爲調用方指定的值nThreads, 超時時間爲0  
  8.     return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,  
  9.             new LinkedBlockingQueue<Runnable>());  
  10. }  
  11.   
  12. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {  
  13.     // 核心線程數由調用方指定, 最大線程數爲Integer.MAX_VALUE, 超時時間爲0  
  14.     return new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new DelayedWorkQueue());  
  15. }   

 

task隊列

線程池內部持有一個task隊列, 當task的提交速度超過task的執行速度時, task將被緩存在task隊列中等待有線程可用時再執行. ThreadPoolExecutor在創建時可以爲其指定task隊列, 開發者一般有三種選擇: 有界隊列, 無界隊列以及同步隊列. Executors.newFixedThreadPool和Executors.newScheduledThreadPool返回的ThreadPoolExecutor對象使用的是無界隊列, 而Executors.newCashedThreadPool返回的ThreadPoolExecutor對象使用的是同步隊列.

爲線程數不多的線程池指定一個容量大的隊列(或者無界隊列), 有助於減少線程間切換, CPU等方面的消耗, 代價是可能會造成吞吐量下降. 如果使用的是有界隊列, 隊列可能會被填滿, 此時將根據指定的飽和策略進行處理(見之後的講述).

對於線程數很大的線程池, 可以使用同步隊列. 同步隊列(SynchronousQueue)其實不能算是一種隊列, 因爲同步隊列沒有緩存的作用. 使用同步隊列時, task被提交時, 直接由線程池中的線程接手. 如果此時線程池中沒有可用的線程, 線程池將創建新的線程接手. 如果線程池無法創建新的線程(比如線程數已到達maximumPoolSize), 則根據指定的飽和策略進行處理(同樣見之後的講述).

 

飽和策略

如果線程池使用的是有界隊列, 那麼當有界隊列滿時繼續提交task時飽和策略會被觸發.

如果線程池使用的是同步隊列, 那麼當線程池無法創建新的線程接手task時飽和策略會被觸發.

如果線程池被關閉後, 仍然向其提交task時, 飽和策略也會被觸發.

ThreadPoolExecutor.setRejectedExecutionHandler方法用於設定飽和策略. 該方法接受一個RejectedExecutionHandler對象作爲參數. RejectedExecutionHandler只定義了一個方法:rejectedExecution(Runnable r, ThreadPoolExecutor executor). rejectedExecution方法在飽和策略被觸發時由系統回調.

ThreadPoolExecutor類中預定義了多個RejectedExecutionHandler的實現類: AbortPolicy, CallerRunsPolicy, DiscardPolicy, 和DiscardOldestPolicy.

AbortPolicy是默認的飽和策略, 其rejectedExecution方法爲:

Java代碼  收藏代碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.     throw new RejectedExecutionException();  
  3. }   

可見默認情況下, 觸發飽和策略時將拋出RejectedExecutionException異常.

CallerRunsPolicy. 飽和時將在提交task的線程中執行task, 而不是由線程池中的線程執行:

Java代碼  收藏代碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.     if (!e.isShutdown()) {  
  3.         r.run();  
  4.     }  
  5. }  

使用CallerRunsPolicy的例子:

Java代碼  收藏代碼
  1. class LifecycleWebServer {  
  2.     // MAX_THREAD_COUNT和MAX_QUEUE_COUNT的值根據系統的實際情況確定  
  3.     private static final int MAX_THREAD_COUNT = 100;  
  4.     private static final int MAX_QUEUE_COUNT = 1000;  
  5.   
  6.     // 使用有界隊列作爲task隊列, 當有界隊列滿時, 將觸發飽和策略  
  7.     private final ThreadPoolExecutor exec = new ThreadPoolExecutor(0, MAX_THREAD_COUNT, 60L, TimeUnit.SECONDS,  
  8.             new ArrayBlockingQueue<Runnable>(MAX_QUEUE_COUNT));  
  9.   
  10.     public void start() throws IOException {  
  11.         // 設置飽和策略爲CallerRunsPolicy  
  12.         exec.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());  
  13.         ServerSocket socket = new ServerSocket(80);  
  14.         while (!exec.isShutdown()) {  
  15.             try {  
  16.                 final Socket conn = socket.accept();  
  17.                 exec.execute(new Runnable() {  
  18.                     public void run() {  
  19.                         handleRequest(conn);  
  20.                     }  
  21.                 });  
  22.             } catch (RejectedExecutionException e) {  
  23.                 if (!exec.isShutdown())  
  24.                     log("task submission rejected", e);  
  25.             }  
  26.         }  
  27.     }  
  28.   
  29.     public void stop() {  
  30.         exec.shutdown();  
  31.     }  
  32.   
  33.     void handleRequest(Socket connection) {  
  34.         Request req = readRequest(connection);  
  35.         if (isShutdownRequest(req))  
  36.             stop();  
  37.         else  
  38.             dispatchRequest(req);  
  39.     }  
  40.       
  41.     public static void main(String[] args) {  
  42.         LifecycleWebServer server = new LifecycleWebServer();  
  43.         try {  
  44.             // 在main線程中啓動server  
  45.             server.start();  
  46.         } catch (IOException e) {  
  47.             e.printStackTrace();  
  48.         }  
  49.     }  
  50. }   

LifecycleWebServer中的線程池使用CallerRunsPolicy作爲其飽和策略. 如果線程池飽和時main線程仍然向線程池提交task, 那麼task將在main中執行. main線程執行task是需要一定時間的, 這樣就給了線程池喘息的機會, 而且main線程在執行task的時間內無法接受socket連接, 因此socket連接請求將緩存在tcp層. 如果server過載持續的時間較長, 使得tcp層的緩存不夠, 那麼tcp緩存將根據其策略丟棄部分請求. 如此一來, 整個系統的過載壓力逐步向外擴散: 線程池-線程池中的隊列-main線程-tcp層-client. 這樣的系統在發生過載時是比較優雅的: 既不會因爲過多的請求而導致系統資源耗盡, 也不會一發生過載時就拒絕服務, 只有發生長時間系統過載時纔會出現客戶端無法連接的情況.

DiscardPolicy. 該策略將最新提交的task丟棄:

Java代碼  收藏代碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.     // 丟棄, 不做任何處理  
  3. }   

DiscardOldestPolicy. 該策略丟棄隊列中處於對頭的task, 且試着再次提交最新的task:

Java代碼  收藏代碼
  1. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {  
  2.     if (!e.isShutdown()) {  
  3.     e.getQueue().poll();  
  4.     e.execute(r);  
  5.     }  
  6. }   

DiscardOldestPolicy與PriorityBlockingQueue結合使用時可能會造成不好的結果, 因爲PriorityBlockingQueue中位於對頭的task是優先級最高的task, 發生飽和時反而首先丟棄優先級高的task可能不符合需求.

ThreadPoolExecutor沒有提供飽和時阻塞的策略, 不過開發者可以結合Semaphore實現:

Java代碼  收藏代碼
  1. public class BoundedExecutor {  
  2.     private final Executor exec;  
  3.     private final Semaphore semaphore;  
  4.   
  5.     public BoundedExecutor(Executor exec, int bound) {  
  6.         this.exec = exec;  
  7.         // 設定信號量permit的上限  
  8.         this.semaphore = new Semaphore(bound);  
  9.     }  
  10.   
  11.     public void submitTask(final Runnable command) throws InterruptedException {  
  12.         // 提交task前先申請permit, 如果無法申請到permit, 調用submitTask的線程將被阻塞, 直到有permit可用  
  13.         semaphore.acquire();  
  14.         try {  
  15.             exec.execute(new Runnable() {  
  16.                 public void run() {  
  17.                     try {  
  18.                         command.run();  
  19.                     } finally {  
  20.                         // 提交成功了, 運行task後釋放permit  
  21.                         semaphore.release();  
  22.                     }  
  23.                 }  
  24.             });  
  25.         } catch (RejectedExecutionException e) {  
  26.             // 如果沒有提交成功, 也需要釋放permit  
  27.             semaphore.release();  
  28.         }  
  29.     }  
  30. }  

 

ThreadFactory

在創建ThreadPoolExecutor時還可以爲其指定ThreadFactory, 當線程池需要創建新的線程時會調用ThreadFactory的newThread方法. 默認的ThreadFactory創建的線程是nonDaemon, 線程優先級爲NORM_PRIORITY的線程, 並且爲其指定了可識別的線程名稱:

Java代碼  收藏代碼
  1. public Thread newThread(Runnable r) {  
  2.     Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);  
  3.     if (t.isDaemon())  
  4.         t.setDaemon(false);  
  5.     if (t.getPriority() != Thread.NORM_PRIORITY)  
  6.         t.setPriority(Thread.NORM_PRIORITY);  
  7.     return t;  
  8. }   

開發者可以根據自身需要爲ThreadPoolExecutor指定自定義的ThreadFactory. 例如:

Java代碼  收藏代碼
  1. public class MyThreadFactory implements ThreadFactory {  
  2.     private final String poolName;  
  3.   
  4.     public MyThreadFactory(String poolName) {  
  5.         this.poolName = poolName;  
  6.     }  
  7.   
  8.     public Thread newThread(Runnable runnable) {  
  9.         return new MyAppThread(runnable, poolName);  
  10.     }  
  11. }  
  12.   
  13. public class MyAppThread extends Thread {  
  14.     public static final String DEFAULT_NAME = "MyAppThread";  
  15.     private static volatile boolean debugLifecycle = false;  
  16.     private static final AtomicInteger created = new AtomicInteger();  
  17.     private static final AtomicInteger alive = new AtomicInteger();  
  18.     private static final Logger log = Logger.getAnonymousLogger();  
  19.   
  20.     public MyAppThread(Runnable r) {  
  21.         this(r, DEFAULT_NAME);  
  22.     }  
  23.   
  24.     public MyAppThread(Runnable runnable, String name) {  
  25.         // 爲自定義的Thread類指定線程名稱  
  26.         super(runnable, name + "-" + created.incrementAndGet());  
  27.         // 設置UncaughtExceptionHandler. UncaughtExceptionHandler的uncaughtException方法將在線程運行中拋出未捕獲異常時由系統調用  
  28.         setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {  
  29.             public void uncaughtException(Thread t, Throwable e) {  
  30.                 log.log(Level.SEVERE, "UNCAUGHT in thread " + t.getName(), e);  
  31.             }  
  32.         });  
  33.     }  
  34.   
  35.     public void run() {  
  36.         // Copy debug flag to ensure consistent value throughout.   
  37.         boolean debug = debugLifecycle;  
  38.         if (debug)  
  39.             log.log(Level.FINE, "Created " + getName());  
  40.         try {  
  41.             alive.incrementAndGet();  
  42.             super.run();  
  43.         } finally {  
  44.             alive.decrementAndGet();  
  45.             if (debug)  
  46.                 log.log(Level.FINE, "Exiting " + getName());  
  47.         }  
  48.     }  
  49.   
  50.     public static int getThreadsCreated() {  
  51.         return created.get();  
  52.     }  
  53.   
  54.     public static int getThreadsAlive() {  
  55.         return alive.get();  
  56.     }  
  57.   
  58.     public static boolean getDebug() {  
  59.         return debugLifecycle;  
  60.     }  
  61.   
  62.     public static void setDebug(boolean b) {  
  63.         debugLifecycle = b;  
  64.     }  
  65. }  

 

擴展ThreadPoolExecutor

ThreadPoolExecutor類提供了多個"鉤子"方法, 以供其子類實現, 比如beforeExecute, afterExecute, terminated等. 所謂"鉤子"是指基類預留的, 但是沒有提供具體實現的方法, 其方法體爲空. 子類可以根據需要爲"鉤子"提供具體實現.

beforeExecute和afterExecute方法分別在執行task前後調用:

Java代碼  收藏代碼
  1. private void runTask(Runnable task) {  
  2.     final ReentrantLock runLock = this.runLock;  
  3.     runLock.lock();  
  4.     try {  
  5.         if (runState < STOP && Thread.interrupted() && runState >= STOP)  
  6.             thread.interrupt();  
  7.         boolean ran = false;  
  8.         beforeExecute(thread, task);  
  9.         try {  
  10.             task.run();  
  11.             ran = true;  
  12.             afterExecute(task, null);  
  13.             ++completedTasks;  
  14.         } catch (RuntimeException ex) {  
  15.             if (!ran)  
  16.                 afterExecute(task, ex);  
  17.             throw ex;  
  18.         }  
  19.     } finally {  
  20.         runLock.unlock();  
  21.     }  
  22. }   

beforeExecute和afterExecute方法可以用於記錄日誌, 統計數據等操作.

terminated方法在線程池被關閉後調用. terminated方法可以用於釋放線程池申請的資源.

發佈了111 篇原創文章 · 獲贊 62 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章