本文譯自:Java Concurrency – Part 7 : Executors and thread pools
讓我們開始來從入門瞭解一下 Java 的並發編程。
本文主要介紹如何開始建立線程以及管理線程池,在 Java 語言中,一個最簡單的線程如下代碼所示:
Runnable runnable = new Runnable(){ public void run(){ System.out.println("Run"); }}
可通過下面一行代碼來啟動這個線程:
new Thread(runnable).start();
這是一個再簡單不過的例子了,但如果你有許多需要長時間啟動並執行任務同時執行,並需要等所有的這些線程都執行完畢,還想得到一個傳回值,那麼這就有點小小難度了。但 Java 已經有解決方案給你,那就是 Executors ,一個簡單的類可以讓你建立線程池和線程工廠。
一個線程池使用類 ExecutorService 的執行個體來表示,通過 ExecutorService 你可以提交任務,並進行調度執行。下面列舉一些你可以通過 Executors 類來建立的線程池的類型: Single Thread Executor : 只有一個線程的線程池,因此所有提交的任務是順序執行,代碼:Executors.newSingleThreadExecutor() Cached 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 extends Callable<String>{ public String call(){ //Long operations return "Run"; }}
如果你想使用4個線程來執行這個任務10次,那麼代碼如下:
ExecutorService pool = Executors.newFixedThreadPool(4); for(int i = 0; i < 10; i++){ pool.submit(new StringTask());}
但你必須手工的關閉線程池來結束所有池中的線程:
pool.shutdown();
如果你不這麼做,JVM 並不會去關閉這些線程;另外你可以使用 shutdownNow() 的方法來強制關閉線程池,那麼執行中的線程也會被中斷,所有尚未被執行的任務也將不會再執行。
但這個例子中,你無法擷取任務的執行狀態,因此我們需要藉助 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(); //Compute the result} pool.shutdown();
不過這段代碼稍微有點複雜,而且有不足的地方。如果第一個任務耗費非常長的時間來執行,然後其他的任務都早於它結束,那麼當前線程就無法在第一個任務結束之前獲得執行結果,但是別著急,Java 為你提供瞭解決方案——CompletionService。
一個 CompletionService 就是一個服務,用以簡化等待任務的執行結果,實現的類是 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(); //Compute the result} threadPool.shutdown(); 通過這段代碼,我們可以根據執行結束的順序擷取對應的結果,而無需維護一個 Future 對象的集合。
這就是本文的全部,通過 Java 為我們提供的各種工具,可以方便的進行多任務的編程,通過使用 Executors、ExecutorService 以及 CompletionService 等工具類,我們可以建立複雜的並行任務執行演算法,而且可以輕鬆改變線程數。
希望這篇短文能有助於你對並發編程的理解。
---
Executor 是 java5 下的一個多任務並發執行架構(Doug Lea),可以建立一個類似資料庫連接池的線程池來執行任務。這個架構主要由三個介面和其相應的具體類組成。Executor、 ExecutorService 和 ScheduledExecutorService 。
1 、 Executor 介面:是用來執行 Runnable 任務的;它只定義一個方法- execute(Runnable command);執行 Ruannable 類型的任務。
2 、 ExecutorService 介面: 繼承Executor介面,提供了執行Callable任務和中止任務執行的服務。
3 、 ScheduledExecutorService 介面:繼承 ExecutorService 介面,提供了按排程執行任務的服務。
4 、 Executors 類:為了方便使用, 建議使用 Executors的工具類來得到 Executor 介面的具體對象。
Executors 類有幾個重要的方法,在這裡簡明一下:
1 、 callable(Runnable task): 將 Runnable 的任務轉化成 Callable 的任務
2 、 newSingleThreadExecutor(): 產生一個 ExecutorService 對象,這個對象只有一個線程可用來執行任務,若任務多於一個,任務將按先後順序執行。
3 、 newCachedThreadPool(): 產生一個 ExecutorService 對象,這個對象帶有一個線程池,線程池的大小會根據需要調整,線程執行完任務後返回線程池,供執行下一次任務使用。
4 、 newFixedThreadPool(int poolSize): 產生一個 ExecutorService 對象,這個對象帶有一個大小為 poolSize 的線程池,若任務數量大於 poolSize ,任務會被放在一個 queue 裡順序執行。
5 、 newSingleThreadScheduledExecutor(): 產生一個 ScheduledExecutorService 對象,這個對象的線程池大小為 1 ,若任務多於一個,任務將按先後順序執行。
6 、 newScheduledThreadPool(int poolSize): 產生一個 ScheduledExecutorService 對象,這個對象的線程池大小為 poolSize ,若任務數量大於 poolSize ,任務會在一個 queue 裡等待執行 。
有關Executor架構其它類的說明請參看JAVA 5 的 API文檔
下面是幾個簡單的例子,用以樣本Executors中幾個主要方法的使用。
1、 Task.java 任務
2、 SingleThreadExecutorTest.java 單線程執行程式的測試
3、 CachedThreadPoolTest.java 線程池線程執行程式的測試
4、 FixedThreadPoolTest.java 線程池線程執行程式的測試(線程數固定)
5、 DaemonThreadFactory.java 守護線程產生工廠
6、 MaxPriorityThreadFactory.java 大優先順序線程產生工廠
7、 MinPriorityThreadFactory.java 小優先順序線程產生工廠
8、 ThreadFactoryExecutorTest.java 在自訂線程產生工廠下的測試
1、 Task.java 任務 Java代碼 package com.thread; //可執行任務 public class Task implements Runnable { // 中斷訊號 volatile boolean stop = false; // 該任務執行的次數 private int runCount = 0; // 任務標識 private int taskId; public Task(int taskId) { this.taskId = taskId; System.out.println("Create Task-" + taskId); } // 執行任務 public void run() { while (!stop) { try { Thread.sleep(10); } catch (InterruptedException e) {