Java 併發 – 第七部分:Executors 與線程池

如今讓咱們開始 Java 併發系列的新篇章。這壹次咱們會學習如何幹淨的啓動壹個新線程,以及如何在線程池中管理它。在 Java 中,假設你有壹個像下面這樣的 Runnable 線程:
Runnable runnable = new Runnable(){
   public void run(){
      System.out.println("Run");
   }
}

你能夠簡單的在壹個線程中運行它: html

new Thread(runnable).start();

這個代碼很簡潔,可是若是咱們想並行的啓動多個任務該怎麼辦呢,並且咱們還想等待全部任務完成以後獲取全部任務的返回值,這樣的話,要想保持較好的代碼風格實際上有點困難了。不過,就像全部其它的難題壹樣,Java 爲咱們提供了壹個解決方案,那就是 Executor ,這個簡單的類容許你建立線程池和線程工廠。 java

ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
    executor.execute(new Runnable() {
        public void run() {
            System.out.println("do something here...");
        }
    });
  }
executor.shutdown();
線程池實際表現爲 ExecutorService 類的壹個實例。經過使用 ExecutorService ,你能夠提交將在將來完成的任務。經過 Executor 類你能夠建立以下幾種線程池:

一、Single Thread Executor:只有壹個線程的線程池。因此全部提交的任務都會被順序執行。方法名稱:Executors.newSingleThreadExecutor();
二、Cache Thread Pool:壹個建立足夠多的線程的線程池,能夠並行的執行全部任務。舊的線程會被從新用於新的任務。若是線程在60秒內沒有被用到,這個線程就會被終止而且從線程池中移除掉。方法名稱:Executors.newCachedThreadPool();
三、Fixed Thread Pool:壹個有着固定數量的線程的線程池。若是對於某個任務而言某線程不可用,這個任務會被放進隊列中等待其它的任務完成以後再執行。方法名稱:Executors.newFixedThreadPool();
四、Scheduled Thread Pool:壹個爲定時任務準備的線程池。方法名稱:Executors.newScheduledThreadPool();
五、Single Thread Scheduled Pool:壹個爲定時的將來的任務準備的單個線程的線程池,方法名稱:Executors.newSingleThreadScheduledExecutor();

壹旦你有了壹個線程池,你可使用上述不一樣的提交方法提交你的任務了。你能夠提交壹個 Runnable 或者 Callable 的任務到線程池中。這個方法返回壹個 Future 對象,用於表示這個任務在將來的狀態。若是你提交壹個 Runnable 的任務,那麼壹旦任務結束, Future 將返回 null 。
舉個例子,若是你有以下這樣壹個 Callable 的任務: 算法

private final class StringTask implements Callable<String> {
   public String call(){
      //耗時的操做
      return "Run";
   }
}

若是你但願使用4個線程執行該任務10次,你可使用以下代碼: api

ExecutorService pool = Executors.newFixedThreadPool(4);

for(int i = 0; i < 10; i++){
   pool.submit(new StringTask());
}
可是你必須關閉線程池才能結束池中的全部線程:
pool.shutdown();
若是你不這樣作,Java 虛擬機就存在不能關閉的風險,由於仍然有線程沒有終止。如今你可使用 shutdown() 方法強制關閉線程池,可是這樣壹來當前正在執行的任務將會被中斷,而沒有啓動的線程永遠都不會再啓動了。
然而,若是使用上面的樣例代碼,你永遠不會得到任務執行後的結果。因此咱們接下來使用 Future 對象來完成這些任務:
ExecutorService pool = Executors.newFixedThreadPool(4);
List<Future<String>> futures = new ArrayList<Future<String>>(10);
for(int i = 0; i < 10; i++){
   futures.add(pool.submit(new StringTask()));
}

for(Future<String> future : futures){
   String result = future.get();
   //計算結果
}

pool.shutdown();
可是使用這種代碼略有點複雜,並且有個缺點。若是第壹個任務花了很長時間去計算,而其它任務在它以前先結束了,當前線程在第壹個線程結果以前就不能計算結果。再壹次,Java 爲咱們提供了壹個解決方案,這就是 CompletionService
CompletionService 是壹個服務,它可讓 executor 等待提交任務的執行結果變得容易。 它的壹個實現 ExecutorCompletionService 是基於 ExecutorService 來完成工做的。因此讓咱們來試試:
ExecutorService threadPool = Executors.newFixedThreadPool(4);
CompletionService<String> pool = new ExecutorCompletionService<String>(threadPool);

for(int i = 0; i < 10; i++){
   pool.submit(new StringTask());
}

for(int i = 0; i < 10; i++){
   String result = pool.take().get();
   //計算結果
}

threadPool.shutdown();
經過這種方式,你能夠按照它們完成任務的順序得到結果而沒必要持有壹個全部 Future 對象的集合。
經過上述介紹,你的手裏如今有了使用線程池來處理並行任務的高性能工具,經過使用 Executors,ExecutorService 和 CompletionService 你能夠建立壹個複雜的算法來使用多個任務。經過使用這些工具,改變並行運行的線程數量,或者增長更多任務而不改變不少代碼也變的容易了。我但願這篇文章可以幫助你寫出更好的並行運算的代碼。

本文英文原文出自 http://www.baptiste-wicht.com/2010/09/java-concurrency-part-7-executors-and-thread-pools/,中文翻譯首發開源中國社區 http://my.oschina.net/bairrfhoinn/blog/167113,轉載請註明原始出處。 併發

2013-10-21 09:54:00 更新:剛剛發現這篇文章的翻譯文章,紅薯已經在以前的討論版塊發過了,連接見於 http://www.oschina.net/question/12_11255 ,貌似我重複造輪子了,汗壹個! oracle

相關文章
相關標籤/搜索