場景:假如你突然想做飯,但是沒有廚具,也沒有食材。網上購買廚具比較方便,食材去超市買更放心。
實現分析:在快遞員送廚具的期間,我們肯定不會閑着,可以去超市買食材。所以,在主線程里面另起一個子線程去網購廚具。
但是,子線程執行的結果是要返回廚具的,而run方法是沒有返回值的。所以,這才是難點,需要好好考慮一下。
模擬代碼:
1 package test; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class FutureCook { 8 9 public static void main(String[] args) throws InterruptedException, ExecutionException { 10 long startTime = System.currentTimeMillis(); 11 // 第一步 網購廚具 12 Callable<Chuju> onlineShopping = new Callable<Chuju>() { 13 14 @Override 15 public Chuju call() throws Exception { 16 System.out.println("第一步:下單"); 17 System.out.println("第一步:等待送貨"); 18 Thread.sleep(5000); // 模擬送貨時間 19 System.out.println("第一步:快遞送到"); 20 return new Chuju(); 21 } 22 23 }; 24 FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping); 25 new Thread(task).start(); 26 // 第二步 去超市購買食材 27 Thread.sleep(2000); // 模擬購買食材時間 28 Shicai shicai = new Shicai(); 29 System.out.println("第二步:食材到位"); 30 // 第三步 用廚具烹飪食材 31 if (!task.isDone()) { // 聯系快遞員,詢問是否到貨 32 System.out.println("第三步:廚具還沒到,心情好就等着(心情不好就調用cancel方法取消訂單)"); 33 } 34 Chuju chuju = task.get(); 35 System.out.println("第三步:廚具到位,開始展現廚藝"); 36 cook(chuju, shicai); 37 38 System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms"); 39 } 40 41 // 用廚具烹飪食材 42 static void cook(Chuju chuju, Shicai shicai) {} 43 44 // 廚具類 45 static class Chuju {} 46 47 // 食材類 48 static class Shicai {} 49 50 }
結果
1 第一步:下單 2 第一步:等待送貨 3 第二步:食材到位 4 第三步:廚具還沒到,心情好就等着(心情不好就調用cancel方法取消訂單) 5 第一步:快遞送到 6 第三步:廚具到位,開始展現廚藝 7 總共用時5005ms
下面具體分析一下這段代碼:
1)把耗時的網購廚具邏輯,封裝到了一個Callable的call方法里面。
public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
Callable接口可以看作是Runnable接口的補充,call方法帶有返回值,並且可以拋出異常。
2)把Callable實例當作參數,生成一個FutureTask的對象,然后把這個對象當作一個Runnable,作為參數另起線程。
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
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; }
這個繼承體系中的核心接口是Future。Future的核心思想是:一個方法f,計算過程可能非常耗時,等待f返回,顯然不明智。可以在調用f的時候,立馬返回一個Future,可以通過Future這個數據結構去控制方法f的計算過程。
這里的控制包括:
get方法:獲取計算結果(如果還沒計算完,也是必須等待的)
cancel方法:還沒計算完,可以取消計算過程
isDone方法:判斷是否計算完
isCancelled方法:判斷計算是否被取消
這些接口的設計很完美,FutureTask的實現注定不會簡單,后面再說。
3)在第三步里面,調用了isDone方法查看狀態,然后直接調用task.get方法獲取廚具,不過這時還沒送到,所以還是會等待3秒。對比第一段代碼的執行結果,這里我們節省了2秒。這是因為在快遞員送貨期間,我們去超市購買食材,這兩件事在同一時間段內異步執行!!!
JDK8 中 CompletableFuture 是非常強大的 Future 的擴展功能。
模擬代碼:
public class CompleteFutureTests {
public static void main(String[] args) throws Exception {
//1
CompletableFuture<String> bookFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("000000000");
return "xanyi000001";
});
//2
CompletableFuture<String> tableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("沉睡5秒");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "xanyi111110";
});
//CompletableFuture.allOf(bookFuture, tableFuture).join();
CompletableFuture.anyOf(bookFuture, tableFuture).join();
System.out.println("book -> " + bookFuture.get());
System.out.println("tale -> " + tableFuture.get());
}
}
allOf 工廠方法接收一個由CompletableFuture 構成的數組,數組中的所有 Completable-Future 對象執行完成之后,它返回一個 CompletableFuture<Void> 對象。這意味着,如果你需要等待多個 CompletableFuture 對象執行完畢,對 allOf 方法返回的
CompletableFuture 執行 join 操作可以等待CompletableFuture執行完成。
或者你可能希望只要 CompletableFuture 對象數組中有任何一個執行完畢就不再等待,在這種情況下,你可以使用一個類似的工廠方法 anyOf 。