有情懷,有干貨,微信搜索【三太子敖丙】關注這個有一點點東西的程式員,
本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章,
前言
在高性能編程中,并發編程已經成為了極為重要的一部分,在單核CPU性能已經趨于極限時,我們只能通過多核來進一步提升系統的性能,因此就催生了并發編程,
由于并發編程比串行編程更困難,也更容易出錯,因此,我們就更需要借鑒一些前人優秀的,成熟的設計模式,使得我們的設計更加健壯,更加完美,
而Future模式,正是其中使用最為廣泛,也是極為重要的一種設計模式,今天就跟阿丙了解一手Future模式!
生活中的Future模式
為了更快的了解Future模式,我們先來看一個生活中的例子,
場景1:
午飯時間到了,同學們要去吃飯了,小王下樓,走了20分鐘,來到了肯德基,點餐,排隊,吃飯一共花了20分鐘,又花了20分鐘走回公司繼續作業,合計1小時,
場景2
午飯時間到了,同學們要去吃飯了,小王點了個肯德基外賣,很快,它就拿到了一個訂單(雖然訂單不能當飯吃,但是有了訂單,還怕吃不上飯嘛),接著小王可以繼續干活,30分鐘后,外賣到了,接著小投訓了10分鐘吃飯,接著又可以繼續作業了,成功的卷到了隔壁的小汪,

很明顯,在這2個場景中,小王的作業時間更加緊湊,特別是那些排隊的時間都可以讓外賣員去干,因此可以更加專注于自己的本職作業,聰明的你應該也已經體會到了,場景1就是典型的函式同步呼叫,而場景2是典型的異步呼叫,
而場景2的異步呼叫,還有一個特點,就是它擁有一個回傳值,這個回傳值就是我們的訂單,這個訂單很重要,憑借著這個訂單,我們才能夠取得當前這個呼叫所對應的結果,
這里的訂單就如同Future模式中的Future,這是一個合約,一份承諾,雖然訂單不能吃,但是手握訂單,不怕沒吃的,雖然Future不是我們想要的結果,但是拿著Future就能在將來得到我們想要的結果,
因此,Future模式很好的解決了那些需要回傳值的異步呼叫,
Future模式中的主要角色
一個典型的Future模式由以下幾個部分組成:
- Main:系統啟動,呼叫Client發出請求
- Client:回傳Data物件,立即回傳FutureData,并開啟ClientThread執行緒裝配RealData
- Data:回傳資料的介面
- FutureData:Future資料,構造很快,但是是一個虛擬的資料,需要裝配RealData,好比一個訂單
- RealData:真實資料,其構造是比較慢的,好比上面例子中的肯德基午餐,
它們之間的相互關系如下圖:

其中,值得注意是Data,RealData和FutureData,這是一組典型的代理模式,Data介面表示對外資料,RealData表示真實的資料,就好比午餐,獲得它的成本比較高,需要很多時間;相對的FutureData作為RealData的代理,類似于一個訂單/契約,通過FutureData,可以在將來獲得RealData,
因此,Future模式本質上是代理模式的一種實際應用,
實作一個簡單的Future模式
根據上面的設計,讓我們來實作一個簡單的代理模式吧!
首先是Data介面,代表資料:
public interface Data {
public String getResult ();
}
接著是FutureData,也是整個Future模式的核心:
public class FutureData implements Data {
// 內部需要維護RealData
protected RealData realdata = null;
protected boolean isReady = false;
public synchronized void setRealData(RealData realdata) {
if (isReady) {
return;
}
this.realdata = realdata;
isReady = true;
//RealData已經被注入,通知getResult()
notifyAll();
}
//會等待RealData構造完成
public synchronized String getResult() {
while (!isReady) {
try {
//一直等待,直到RealData被注入
wait();
} catch (InterruptedException e) {
}
}
//真正需要的資料從RealData獲取
return realdata.result;
}
}
下面是RealData:
public class RealData implements Data {
protected final String result;
public RealData(String para) {
StringBuffer sb=new StringBuffer();
//假設這里很慢很慢,構造RealData不是一個容易的事
result =sb.toString();
}
public String getResult() {
return result;
}
}
然后從Client得到Data:
public class Client {
//這是一個異步方法,回傳的Data介面是一個Future
public Data request(final String queryStr) {
final FutureData future = new FutureData();
new Thread() {
public void run() {
// RealData的構建很慢,所以在單獨的執行緒中進行
RealData realdata = new RealData(queryStr);
//setRealData()的時候會notify()等待在這個future上的物件
future.setRealData(realdata);
}
}.start();
// FutureData會被立即回傳,不會等待RealData被構造完
return future;
}
}
最后一個Main函式,把所有一切都串起來:
public static void main(String[] args) {
Client client = new Client();
//這里會立即回傳,因為得到的是FutureData而不是RealData
Data data = client.request("name");
System.out.println("請求完畢");
try {
//這里可以用一個sleep代替了對其他業務邏輯的處理
//在處理這些業務邏輯的程序中,RealData被創建,從而充分利用了等待時間
Thread.sleep(2000);
} catch (InterruptedException e) {
}
//使用真實的資料,如果到這里資料還沒有準備好,getResult()會等待資料準備完,再回傳
System.out.println("資料 = " + data.getResult());
}
這是一個最簡單的Future模式的實作,雖然簡單,但是已經包含了Future模式中最精髓的部分,對大家理解JDK內部的Future物件,有著非常重要的作用,
Java中的Future模式
Future模式是如此常用,在JDK內部已經有了比較全面的實作和支持,下面,讓我們一起看看JDK內部的Future實作:

首先,JDK內部有一個Future介面,這就是類似前面提到的訂單,當然了,作為一個完整的商業化產品,這里的Future的功能更加豐富了,除了get()方法來獲得真實資料以外,還提供一組輔助方法,比如:
- cancel():如果等太久,你可以直接取消這個任務
- isCancelled():任務是不是已經取消了
- isDone():任務是不是已經完成了
- get():有2個get()方法,不帶引數的表示無窮等待,或者你可以只等待給定時間
下面代碼演示了這個Future的使用方法:
//異步操作 可以用一個執行緒池
ExecutorService executor = Executors.newFixedThreadPool(1);
//執行FutureTask,相當于上例中的 client.request("name") 發送請求
//在這里開啟執行緒進行RealData的call()執行
Future<String> future = executor.submit(new RealData("name"));
System.out.println("請求完畢,資料準備中");
try {
//這里依然可以欄位外的資料操作,這里使用sleep代替其他業務邏輯的處理
Thread.sleep(2000);
} catch (InterruptedException e) {
}
//如果此時call()方法沒有執行完成,則依然會等待
System.out.println("資料 = " + future.get());
整個使用程序非常簡單,下面我們來分析一下executor.submit()里面究竟發生了什么:
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
// 根據Callable物件,創建一個RunnableFuture,這里其實就是FutureTask
RunnableFuture<T> ftask = newTaskFor(task);
//將ftask推送到執行緒池
//在新執行緒中執行的,就是run()方法,在下面的代碼中有給出
execute(ftask);
//回傳這個Future,將來通過這個Future就可以得到執行的結果
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
最關鍵的部分在下面,FutureTask作為一個執行緒單獨執行時,會將結果保存到outcome中,并設定任務的狀態,下面是FutureTask的run()方法:

從FutureTask中獲得結果的實作如下:
public V get() throws InterruptedException, ExecutionException {
int s = state;
//如果沒有完成,就等待,回到用park()方法阻塞執行緒
//同時,所有等待執行緒會在FutureTask的waiters欄位中排隊等待
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
//outcome里保存的就是最終的計算結果
Object x = outcome;
if (s == NORMAL)
//正常完成,就回傳outcome
return (V)x;
//如果沒有正常完成, 比如被用戶取消了,或者有例外了,就拋出例外
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
Future模式的高階版本—— CompletableFuture
Future模式雖然好用,但也有一個問題,那就是將任務提交給執行緒后,呼叫執行緒并不知道這個任務什么時候執行完,如果執行呼叫get()方法或者isDone()方法判斷,可能會進行不必要的等待,那么系統的吞吐量很難提高,
為了解決這個問題,JDK對Future模式又進行了加強,創建了一個CompletableFuture,它可以理解為Future模式的升級版本,它最大的作用是提供了一個回呼機制,可以在任務完成后,自動回呼一些后續的處理,這樣,整個程式可以把“結果等待”完全給移除了,
下面來看一個簡單的例子:

在這個例子中,首先以getPrice()為基礎創建一個異步呼叫,接著,使用thenAccept()方法,設定了一個后續的操作,也就是當getPrice()執行完成后的后續處理,
不難看到,CompletableFuture比一般的Future更具有實用性,因為它可以在Future執行成功后,自動回呼進行下一步的操作,因此整個程式不會有任何阻塞的地方(也就是說你不用去到處等待Future的執行,而是讓Future執行成功后,自動來告訴你),
以上面的代碼為例,CompletableFuture之所有會有那么神奇的功能,完全得益于AsyncSupply類(由上述代碼中的supplyAsync()方法創建),
AsyncSupply在執行時,如下所示:
public void run() {
CompletableFuture<T> d; Supplier<T> f;
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
if (d.result == null) {
try {
//這里就是你要執行的異步方法
//結果會被保存下來,放到d.result欄位中
d.completeValue(f.get());
} catch (Throwable ex) {
d.completeThrowable(ex);
}
}
//執行成功了,進行后續處理,在這個后續處理中,就會呼叫thenAccept()中的消費者
//這里就相當于Future完成后的通知
d.postComplete();
}
}
繼續看d.postComplete(),這里會呼叫后續一系列操作
final void postComplete() {
//省略部分代碼,重點在tryFire()里
//在tryFire()里,真正觸發了后續的呼叫,也就是thenAccept()中的部分
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}
絮叨
今天,我們主要介紹Future模式,我們從一個最簡單的Future模式開始,逐步深入,先后介紹了JDK內部的Future模式實作,以及對Future模式的進化版本CompletableFuture做了簡單的介紹,對
于多執行緒開發而言,Future模式的應用極其廣泛,可以說這個模式已經成為了異步開發的基礎設施,
好啦如果想了解多執行緒的更多知識點可以關注我,查看歷史文章,我也會持續更新的,
敖丙把自己的面試文章整理成了一本電子書,共 1630頁!
干貨滿滿,字字精髓,目錄如下,還有我復習時總結的面試題以及簡歷模板,現在免費送給大家,

鏈接:https://pan.baidu.com/s/1ZQEKJBgtYle3v-1LimcSwg 密碼:wjk6
我是敖丙,你知道的越多,你不知道的越多,感謝各位人才的:點贊、收藏和評論,我們下期見!
文章持續更新,可以微信搜一搜「 三太子敖丙 」第一時間閱讀,回復【資料】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/275396.html
標籤:AI
