17.併發程式設計--執行緒池
阿新 • • 發佈:2018-11-20
併發程式設計執行緒池
合理利用執行緒池能夠帶來三個好處。
- 第一:降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
- 第二:提高響應速度。當任務到達時,任務可以不需要的等到執行緒建立就能立即執行。
- 第三:提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。但是要做到合理的利用執行緒池,必須對其原理了如指掌。
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 }