本文主要講解平時開發(fā)中常用的異步編程的接口:Callable和Future,。
創(chuàng)建線程的2種方式,,一種是直接繼承Thread,,另外一種就是實現(xiàn)Runnable接口。這2種方式都有一個缺陷就是:在執(zhí)行完任務(wù)之后無法獲取執(zhí)行結(jié)果,。如果需要獲取執(zhí)行結(jié)果,,就必須通過共享變量或者使用線程通信的方式來達(dá)到效果,這樣使用起來就比較麻煩,。 自從Java 1.5開始,,就提供了Callable 和Future ,通過它們可以在任務(wù)執(zhí)行完畢之后得到任務(wù)執(zhí)行結(jié)果,。
CallableCallable 接口的定義如下:
1 2 3 4
| public interface Callable<V> { V call() throws Exception; }
|
Callable 中定義了 call() 方法計算結(jié)果,,或者當(dāng)不能執(zhí)行的時候拋出異常,可以看到返回值的類型是通過傳入的泛型決定,。
Callable 并不像Runnable 那樣通過Thread的start方法就能啟動實現(xiàn)類的run方法,,所以它通常利用ExecutorService 的submit方法去啟動call方法自執(zhí)行任務(wù),而ExecutorService 的submit又返回一個Future類型的結(jié)果,,因此Callable通常也與Future一起使用
1 2 3 4 5 6
| ExecutorService pool = Executors.newCachedThreadPool(); Future<String> future = pool.submit(new Callable { public void call(){ //your operations } });
|
vs RunnableRunnable 與Callable 不同點:
Runnable 不返回任務(wù)執(zhí)行結(jié)果,,Callable 可返回任務(wù)執(zhí)行結(jié)果;Callable 在任務(wù)無法計算結(jié)果時拋出異常,,而Runnable 不能,;- Callable支持泛型,,Runnable不支持;
Runnable 任務(wù)可直接由Thread的start方法或ExecutorService 的submit方法去執(zhí)行,。
FutureFuture保存異步計算的結(jié)果,可以在我們執(zhí)行任務(wù)時去做其他工作,,并提供了以下幾個方法:
1 2 3 4 5 6 7 8 9 10 11 12
| public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
|
- cancel(boolean mayInterruptIfRunning):試圖取消執(zhí)行的任務(wù),參數(shù)為true時直接中斷正在執(zhí)行的任務(wù),,否則直到當(dāng)前任務(wù)執(zhí)行完成,,成功取消后返回true,否則返回false
- isCancel():判斷任務(wù)是否在正常執(zhí)行完前被取消的,,如果是則返回true
- isDone():判斷任務(wù)是否已完成
- get():等待計算結(jié)果的返回,,如果計算被取消了則拋出
- get(long timeout,TimeUtil unit):設(shè)定計算結(jié)果的返回時間,如果在規(guī)定時間內(nèi)沒有返回計算結(jié)果則拋出TimeOutException
使用Future的好處:
- 獲取任務(wù)的結(jié)果,,判斷任務(wù)是否完成,,中斷任務(wù)
- Future的get方法很好的替代的了Thread.join或Thread,join(long millis)
- Future的get方法可以判斷程序代碼(任務(wù))的執(zhí)行是否超時
FutureTaskFutureTask 實現(xiàn)了RunnableFuture<V> 接口,關(guān)于該接口看一下其定義:
1 2 3 4
| public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
|
RunnableFuture 同時繼承了Runnable 和Future ,,接口中定義的run 方法,,將計算的結(jié)果設(shè)置到Future除非計算被取消。所以它既可以作為Runnable被線程執(zhí)行,,又可以作為Future得到Callable的返回值,。
FutureTask 可以直接提交給Executor 執(zhí)行,當(dāng)然也可以調(diào)用線程直接執(zhí)行FutureTask.run() ,。
FutureTask 是一個可取消的異步計算,,FutureTask 實現(xiàn)了Future的基本方法,提供start和cancel 操作,,可以查詢計算是否已經(jīng)完成,,并且可以獲取計算的結(jié)果。 結(jié)果只可以在計算完成之后獲取,,get方法會阻塞當(dāng)計算沒有完成的時候,,一旦計算已經(jīng)完成, 那么計算就不能再次啟動或是取消,。
使用示例示例場景:有一個耗時的操作,,操作完后會返回一個結(jié)果(不管是正常結(jié)果還是異常),程序如果想擁有比較好的性能不可能由線程去等待操作的完成,,而是應(yīng)該采用listener模式,。jdk并發(fā)包里的Future代表了未來的某個結(jié)果,當(dāng)我們向線程池中提交任務(wù)的時候會返回該對象,。 提交一個Callable 對象給線程池時,,將得到一個Future對象,并且它和傳入的Callable有相同的結(jié)果類型聲明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class FutureTest { public static void main(String[] args) throws Throwable, ExecutionException { ExecutorService executor = Executors.newFixedThreadPool(2); //Java8 的lambada表達(dá)式,,參數(shù)為Callable<T> task Future<String> f = executor.submit(() -> { System.out.println("task started!"); Thread.sleep(1000); return "worker task finished!"; }); System.out.println("Future is ready: " + f.isDone()); //此處阻塞main線程 System.out.println(f.get()); executor.shutdown(); System.out.println("main thread is finished,!"); } //FutureTask的寫法 public static void test() throws Throwable, ExecutionException { FutureTask<String> f = new FutureTask<>(() -> { System.out.println("task started!"); Thread.sleep(1000); return "worker task finished!"; }); Thread thread = new Thread(f); thread.start(); System.out.println("Future is ready: " + f.isDone()); //此處阻塞main線程 System.out.println(f.get()); System.out.println("main thread is finished!"); } }
|
運(yùn)行結(jié)果如下:
1 2 3 4
| Future is ready: false task started! worker task finished! main thread is finished,!
|
如果想獲得耗時操作的結(jié)果,,可以通過get方法獲取,但是該方法會阻塞當(dāng)前線程,,我們可以在做完剩下的某些工作的時候調(diào)用get方法試圖去獲取結(jié)果,,也可以調(diào)用非阻塞的方法isDone來確定操作是否完成。過程如下:
Future代表了線程執(zhí)行完以后的結(jié)果,,可以通過future獲得執(zhí)行的結(jié)果,。但是jdk1.8之前的Future不支持,并不能實現(xiàn)真正的異步,,需要阻塞的獲取結(jié)果,,或者不斷的輪詢。通常我們希望當(dāng)線程執(zhí)行完一些耗時的任務(wù)后,,能夠自動的通知我們結(jié)果,,很遺憾這在原生jdk1.8之前 是不支持的,但是我們可以通過第三方的庫實現(xiàn)真正的異步回調(diào),。如Guava何Netty中都有提供,,讀者可以自己進(jìn)行擴(kuò)展,下面我們看一下JDK8中的實現(xiàn),。
Java8 CompletableFuture 雖然Future以及相關(guān)使用方法提供了異步執(zhí)行任務(wù)的能力,,但是對于結(jié)果的獲取卻是很不方便,,只能通過阻塞或者輪詢的方式得到任務(wù)的結(jié)果,。阻塞的方式顯然和我們的異步編程的初衷相違背,輪詢的方式又會耗費(fèi)無謂的CPU資源,,而且也不能及時地得到計算結(jié)果,。 CompletableFuture類實現(xiàn)了CompletionStage和Future接口,所以你還是可以像以前一樣通過阻塞或者輪詢的方式獲得結(jié)果,,盡管這種方式不推薦使用,。這里使用CompletableFuture實現(xiàn)異步的操作.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Java8PromiseTest { public static void main(String[] args) throws Throwable, ExecutionException { ExecutorService executor = Executors.newFixedThreadPool(2); //jdk1.8,通過調(diào)用給定的Supplier,,異步完成executor中的task CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { System.out.println("task started!"); try { //模擬耗時操作 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return "worker task is finished!"; }, executor); //采用lambada的實現(xiàn)方式 future.thenAccept(e -> { System.out.printf("%s ok", e); executor.shutdown(); }); System.out.println("main thread is finished,!"); } }
|
運(yùn)行結(jié)果如下:
1 2 3
| task started! main thread is finished! worker task is finished! ok
|
supplyAsync 方法以Supplier函數(shù)式接口類型為參數(shù),CompletableFuture的計算結(jié)果類型為U,。thenAccept 方法是CompletableFuture提供的一種處理結(jié)果的方法,,只對結(jié)果執(zhí)行Action,而不返回新的計算值,因此計算值為Void。
總結(jié)本文主要介紹了異步編程經(jīng)常使用的Callable,、Future以及FutureTask,。運(yùn)行Callable任務(wù)可以拿到一個Future對象,F(xiàn)uture 表示異步計算的結(jié)果,。它提供了檢查計算是否完成的方法,,以等待計算的完成,并獲取計算的結(jié)果,。計算完成后只能使用 get 方法來獲取結(jié)果,,如果線程沒有執(zhí)行完,F(xiàn)uture.get()方法可能會阻塞當(dāng)前線程的執(zhí)行,;如果線程出現(xiàn)異常,,Future.get() 會拋出異常。 取消由cancel 方法來執(zhí)行,。isDone確定任務(wù)是正常完成還是被取消了,。一旦計算完成,就不能再取消計算,。如果為了可取消性而使用 Future 但又不提供可用的結(jié)果,,則可以聲明Future<?> 形式類型、并返回 null 作為底層任務(wù)的結(jié)果,。
參考- java異步編程
- Java并發(fā)編程:Callable,、Future和FutureTask
|