1. 程式人生 > >17.併發程式設計--執行緒池

17.併發程式設計--執行緒池

併發程式設計執行緒池

合理利用執行緒池能夠帶來三個好處。

  • 第一:降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
  • 第二:提高響應速度。當任務到達時,任務可以不需要的等到執行緒建立就能立即執行。
  • 第三:提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。但是要做到合理的利用執行緒池,必須對其原理了如指掌。

1. Executor 框架簡介

在 Java 5 之後,併發程式設計引入了一堆新的啟動、排程和管理 執行緒的API。

  • Executor 框架便是 Java 5 中引入的,其內部使用了執行緒池機制,它在 java.util.cocurrent 包下,通過該框架來控制執行緒的啟動、執行和關閉,可以簡化併發程式設計的操作。因此,在 Java 5之後,通過 Executor 來啟動執行緒比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用執行緒池實現,節約開銷)外,還有關鍵的一點:有助於避免 this 逃逸問題——如果我們在構造器中啟動一個執行緒,因為另一個任務可能會在構造器結束之前開始執行,此時可能會訪問到初始化一半的物件用 Executor 在構造器中。

Executor 框架包括:執行緒池,Executor,Executors,ExecutorService,CompletionService,Future,Callable 等。

  • Executor 介面中之定義了一個方法 execute(Runnable command),該方法接收一個 Runable 例項,它用來執行一個任務,任務即一個實現了 Runnable 介面的類。
  • ExecutorService 介面繼承自 Executor 介面,它提供了更豐富的實現多執行緒的方法,比如,ExecutorService 提供了關閉自己的方法,以及可為跟蹤一個或多個非同步任務執行狀況而生成 Future 的方法。
  • 可以呼叫 ExecutorService 的 shutdown()方法來平滑地關閉 ExecutorService,呼叫該方法後,將導致 ExecutorService 停止接受任何新的任務且等待已經提交的任務執行完成(已經提交的任務會分兩類:一類是已經在執行的,另一類是還沒有開始執行的),
  • 當所有已經提交的任務執行完畢後將會關閉 ExecutorService。因此我們一般用該介面來實現和管理多執行緒。

ExecutorService 的生命週期包括三種狀態:執行、關閉、終止

  • 建立後便進入執行狀態,當呼叫了 shutdown()方法時,便進入關閉狀態,此時意味著 ExecutorService
  • 不再接受新的任務,但它還在執行已經提交了的任務,當素有已經提交了的任務執行完後,便到達終止狀態。
  • 如果不呼叫 shutdown()方法,ExecutorService 會一直處在執行狀態,不斷接收新的任務,執行新的任務,伺服器端一般不需要關閉它,保持一直執行即可。

Executors 提供了一系列工廠方法用於創先執行緒池,返回的執行緒池都實現了 ExecutorService 介面

[x] public static ExecutorService newFixedThreadPool(int nThreads)

  • 建立固定數目執行緒的執行緒池。
  • newFixedThreadPool 與 cacheThreadPool 差不多,也是能 reuse 就用,但不能隨時建新的執行緒。。
  • 其獨特之處:任意時間點,最多隻能有固定數目的活動執行緒存在,此時如果有新的執行緒要建立,只能放在另外的佇列中等待,直到當前的執行緒中某個執行緒終止直接被移出池子。
  • 和 cacheThreadPool 不同,FixedThreadPool 沒有 IDLE 機制(可能也有,但既然文件沒提,肯定非常長,類似依賴上層的 TCP 或 UDP IDLE 機制之類的),所以 FixedThreadPool 多數針對一些很穩定很固定的正規併發執行緒,多用於伺服器。
  • 從方法的原始碼看,cache池和fixed 池呼叫的是同一個底層 池,只不過引數不同:
  • fixed 池執行緒數固定,並且是0秒IDLE(無IDLE)。
  • cache 池執行緒數支援 0Integer.MAX_VALUE(顯然完全沒考慮主機的資源承受能力),60 秒 IDLE 。

[x] public static ExecutorService newCachedThreadPool()

  • 建立一個可快取的執行緒池,呼叫execute將重用以前構造的執行緒(如果執行緒可用)。如果現有執行緒沒有可用的,則建立一個新執行緒並新增到池中。
  • 終止並從快取中移除那些已有 60 秒鐘未被使用的執行緒
  • 快取型池子,先檢視池中有沒有以前建立的執行緒,如果有,就 reuse 如果沒有,就建一個新的執行緒加入池中
  • 快取型池子通常用於執行一些生存期很短的非同步型任務 因此在一些面向連線的 daemon 型 SERVER 中用得不多。但對於生存期短的非同步任務,它是 Executor 的首選能 reuse 的執行緒,必須是 timeout IDLE 內的池中執行緒,預設 timeout 是 60s,超過這個 IDLE 時長,執行緒例項將被終止及移出池。
  • 注意,放入 CachedThreadPool 的執行緒不必擔心其結束,超過 TIMEOUT 不活動,其會自動被終止

[x] public static ExecutorService newSingleThreadExecutor()

  • 建立一個單執行緒化的Executor。
  • 單例執行緒,任意時間池中只能有一個執行緒
  • 用的是和 cache 池和 fixed 池相同的底層池,但執行緒數目是 11,0 秒 IDLE(無 IDLE)

[x] public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

  • 建立一個支援定時及週期性的任務執行的執行緒池,多數情況下可用來替代Timer類。
  • 排程型執行緒池
  • 這個池子裡的執行緒可以按 schedule 依次 delay 執行,或週期執行
  • 這四種方法都是用的 Executors 中的 ThreadFactory 建立的執行緒

例項程式碼
Executor 執行 Runnable 任務
通過 Executors 的以上四個靜態工廠方法獲得 ExecutorService 例項,而後呼叫該例項的 execute(Runnable command)方法即可。
一旦 Runnable 任務傳遞到 execute()方法,該方法便會自動在一個執行緒上執行。
下面是 Executor 執行 Runnable 任務的示例程式碼:

 1 import java.util.concurrent.ExecutorService;   
 2 import java.util.concurrent.Executors;   
 3 
 4 public class TestCachedThreadPool{   
 5   public static void main(String[] args){   
 6       ExecutorService executorService = Executors.newCachedThreadPool();   
 7 //      ExecutorService executorService = Executors.newFixedThreadPool(5);  
 8 //      ExecutorService executorService = Executors.newSingleThreadExecutor();  
 9       for (int i = 0; i < 5; i++){   
10           executorService.execute(new TestRunnable());   
11           System.out.println("************* a" + i + " *************");   
12       }   
13       executorService.shutdown();   
14   }   
15 }   
16 
17 class TestRunnable implements Runnable{   
18   public void run(){   
19       System.out.println(Thread.currentThread().getName() + "執行緒被呼叫了。");   
20   }   
21 }