讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議124:異步運算考慮使用Callable接口 >

建議124:異步運算考慮使用Callable接口

多線程應用有兩種實現方式,一種是實現Runnable接口,另一種是繼承Thread類,這兩個方式都有缺點:run方法沒有返回值,不能拋出異常(這兩個缺點歸根到底是Runable接口的缺陷,Thread也是實現了Runnable接口),如果需要知道一個線程的運行結果就需要用戶自行設計,線程類自身也不能提供返回值和異常。但是從Java 1.5開始引入了一個新的接口Callable,它類似於Runable接口,實現它就可以實現多線程任務,Callable的接口定義如下:


public interface Callable<V>{

//具有返回值,並可拋出異常

V call()throws Exception;

}


實現Callable接口的類,只是表明它是一個可調用的任務,並不表示它具有多線程運算能力,還是需要執行器來執行的。我們先編寫一個任務類,代碼如下:


//稅款計算器

class TaxCalculator implements Callable<Integer>{

//本金

private int seedMoney;

//接收主線程提供的參數

public TaxCalculator(int_seedMoney){

seedMoney=_seedMoney;

}

@Override

public Integer call()throws Exception{

//複雜計算,運行一次需要10秒

TimeUnit.MILLISECONDS.sleep(10000);

return seedMoney/10;

}

}


這裡模擬了一個複雜運算:稅款計算器,該運算可能要花費10秒鐘的時間,此時不能讓用戶一直等著吧,需要給用戶輸出點什麼,讓用戶知道系統還在運行,這也是系統友好性的體現:用戶輸入即有輸出,若耗時較長,則顯示運算進度。如果我們直接計算,就只有一個main線程,是不可能有友好提示的,如果稅金不計算完畢,也不會執行後續動作,所以此時最好的辦法就是重啟一個線程來運算,讓main線程做進度提示,代碼如下:


public static void main(Stringargs)throws Exception{

//生成一個單線程的異步執行器

ExecutorService es=Executors.newSingleThreadExecutor();

//線程執行後的期望值

Future<Integer>future=es.submit(new TaxCalculator(100));

while(!future.isDone()){

//還沒有運算完成,等待200毫秒

TimeUnit.MILLISECONDS.sleep(200);

//輸出進度符號

System.out.print("#");

}

System.out.println("\n計算完成,稅金是:"+future.get()+"元");

es.shutdown();

}


在該段代碼中,Executors是一個靜態工具類,提供了異步執行器的創建能力,如單線程執行器newSingleThreadExecutor、固定線程數量的執行器newFixedThreadPool等,一般它是異步計算的入口類。Future關注的是線程執行後的結果,比如有沒有運行完畢,執行結果是多少等。此段代碼的運行結果如下所示:


##################################################

計算完成,稅金是:10元


執行時,「#」會依次遞增,表示系統正在運算,為用戶提供了運算進度。此類異步計算的好處是:

盡可能多地佔用系統資源,提供快速運算。

可以監控線程執行的情況,比如是否執行完畢、是否有返回值、是否有異常等。

可以為用戶提供更好的支持,比如例子中的運算進度等。