ExecutorService 是個介面,繼承了 Executor 介面,Executor 介面只定義了一個稱為 execute() 的 method,實作 Runnable 的類別物件,可以將本身委派給實作 ExecutorService 的類別物件產生另一個執行緒,進行平行運算。
接下來看看範例程式;
1 package idv.steven.concurrency; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 public class ExecutorDemo { 7 8 public static void main(String[] args) { 9 ExecutorService executorService = Executors.newSingleThreadExecutor(); 10 11 executorService.execute(new Runnable() { 12 public void run() { 13 System.out.println("Asynchronous task"); 14 } 15 }); 16 17 executorService.shutdown(); 18 } 19 }程式說明:
- Executors 是一個工廠類別,定義了許多 static 的 method,用來產生與 java.util.cuncurrent 套件中相關的類別,這裡 (line 9) 用來產生一個 ExecutorService 的物件。
- 在第 11~15 行中,有一個匿名的 Runnable 物件,當然,如果程式很大,就不要用匿名的寫法,這裡因為只是一行 output,用匿名比較簡單。這個物件傳入 executorService 物件中會產生一個新的 thread。
- executorService 執行完要自行關閉,即第 17 行呼叫 shutdown(),呼叫 shutdown() 後,就不能再傳別的 Runnable 給這個 executorService 物件了,否則會產生 exception。
- 第 9 行也可以改寫成 ExecutorService executorService = Executors.newFixedThreadPool(10); 一般來說,會使用 thread pool 的狀況是,程式會有很多 thread,且這些 thread 的執行時間很短,在這種狀況下要系統一直建立新的 thread 顯然會花很多時間,所以使用 thread pool。另外,任何一個系統也不能無限制的建立 thread,否則各個 thread 間頻繁的 context switch 反而會拖垮系統效能,所以會給它一個最大值。
- ExecutorService 中的 shutdown() 和 shutdownNow() 這兩個 method 有什麼差別? shutdown() 會執行完已委派的 Runnable 物件後才將 ExecutorService 關閉,shutdownNow 則會立刻關閉,尚未被執行的 Runnable 物件則以 List<Runnable> 傳回。
上面的程式我們要來改寫一下,注意看類別圖,ExecutorService 中有兩個 submit() method,一個傳入的參數是 Runnable 物件,另一個傳入的是 Callable 物件,使用 submit 也可以產生一個新的執行緒,先看一下傳入 Runnable 物件的程式。
1 package idv.steven.concurrency; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.Future; 6 7 public class ExecutorDemo { 8 9 public static void main(String[] args) { 10 ExecutorService executorService = Executors.newSingleThreadExecutor(); 11 12 Future future = executorService.submit(new Runnable() { 13 public void run() { 14 System.out.println("Asynchronous task"); 15 } 16 }); 17 18 executorService.shutdown(); 19 } 20 }修改的部份是第 12~ 16 行,得到的結果與第一個程式是完全一樣的! 再來看一下傳入 Callable 物件的程式。
1 package idv.steven.concurrency; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.ExecutorService; 6 import java.util.concurrent.Executors; 7 import java.util.concurrent.Future; 8 9 public class ExecutorDemo { 10 11 public static void main(String[] args) throws InterruptedException, ExecutionException { 12 ExecutorService executorService = Executors.newSingleThreadExecutor(); 13 14 Future future = executorService.submit(new Callable(){ 15 public Object call() throws Exception { 16 System.out.println("Asynchronous Callable"); 17 return "Callable Result"; 18 } 19 }); 20 21 System.out.println("future.get() = " + future.get()); 22 23 executorService.shutdown(); 24 } 25 }傳入 Runnable 物件和傳入 Callable 物件最大的差別就在於,傳入 Callable 物件可以有傳回值,所以當 21 行呼叫 future.get() 時,可以取得 call() 的傳回值 (line 17),如果是傳入 Runnable 物件,呼叫 get() method 會傳回 null。
沒有留言:
張貼留言