我們來看看Executor介面的內容:
package java.util.concurrent;
public interface Executor(){
void execute(Runnable command);
}
一開始看到這個Executor的時候真的超困惑的,為什麼要用這個介面?我定義好的Runnable就丟進Thread來start()就好了啊~何必再搞一個Executor把Runnable丟進去勒?
這就是一個很好的思考點,假若我們不用這個Executor,如果我們想開10個執行緒來跑相同的Runnable,那我們大概會弄個for迴圈來做:
for(int i = 0; i < 10; i++){
new Thread(someTask).start();
}
抑或是想弄個執行緒池的概念(相當於資料庫的連線池),我只要使用10個Thread實例來跑可能100個任務,不要每個任務都開新的Thread來跑。這在沒有Executor出現以前,我們必須手動寫出這樣的程式,光想像應該也知道不是這麼好寫吧?肯定會有很多流程控制要搞。
而在Executor介面出現以後,它把Runnable任務和Thread實例的指派與操作之間隔了一層execute()的抽象方法出來,在這個execute方法中我們就可以定義Runnable任務和Thread實例的關係了!而官方Java API就提供了很多現成、覆寫(override)好execute()方法的類別讓我們直接使用!比如上一段提到的執行緒池,我們只要如下撰寫:
public class Something(){
void doSomething(Executor executor){
executor.execute(() -> {
do some thing
});
}
}
public static void main(String[] args){
Executor executorService = Executors.newFixedThreadPool(10);
Something some = new Something();
some.doSomething(executorService);
}
在我們真正要做任務的Something類別中我們用Executor的execute方法來定義要做的Runnable任務,而在Main方法要執行時,創建出我們需要的Execute服務(官方API提供了各種Execute服務,不是只有上面範例這個),並指派進Something類別中,程式執行時就會使用我們指派的Execute服務進行程式了!這邊我們採用的是newFixedThreadPool,可以指定需要創建的執行緒數量,程式實際就只會創建出指派數量的執行緒來重複利用。
這種隔一層的設計讓我想到過去做專題時用過的DAO介面,我們在Service中寫的都是定義為DAO介面型別的變數,而實際指派給變數的實例可以是連線池版本的DAO,或者是純JDBC版本輸入帳號密碼創建連線的DAO,但不管指派哪種實例給變數,都不會影響Service實作的程式碼!因為只要透過介面定義的方法及其方法簽章,就能預期輸入以及輸出的東西是什麼了。官方Java API也利用這樣隔一層的方式推出了Executor介面,程式中都可以先定義這個Executor,而實際要使用哪一種Executor實例就都可以再抽換。