多線程異步調用之Future模式

多線程異步調用之Future模式

一、什麼是異步調用

當我們調用一個函數的時候,如果這個函數的執行過程是很耗時的,我們就必須要等待,但是我們有時候並不急著要這個函數返回的結果。因此,我們可以讓被調者立即返回,讓他在後臺慢慢的處理這個請求。對於調用者來說,則可以先處理一些其他事情,在真正需要數據的時候再去嘗試獲得需要的數據(這個真正需要數據的位置也就是上文提到的阻塞點)。這也是Future模式的核心思想:異步調用。

到了這裡,你可能會想CountDownLatch不是也可以實現類似的功能的嗎?也是可以讓耗時的任務通過子線程的方式去執行,然後設置一個阻塞點等待返回的結果,情況貌似是這樣的!但有時發現CountDownLatch只知道子線程的完成情況是不夠的,如果在子線程完成後獲取其計算的結果,那CountDownLatch就有些捉襟見襯了,所以JDK提供的Future類,不僅可以在子線程完成後收集其結果,還可以設定子線程的超時時間,避免主任務一直等待。

看到這裡,似乎恍然大悟了!CountDownLatch無法很好的洞察子線程執行的結果,使用Future就可以完成這一操作,那麼Future何方神聖!下邊我們就細細聊一下。

二、Future模式

雖然,Future模式不會立即返回你需要的數據,但是,他會返回一個契約 ,以後在使用到數據的時候就可以通過這個契約獲取到需要的數據。

多線程異步調用之Future模式

上圖顯示的是一個串行程序調用的流程,可以看出當有一個程序執行的時候比較耗時的時候,其他程序必須等待該耗時操作的結束,這樣的話客戶端就必須一直等待,知道返回數據才執行其他的任務處理。

多線程異步調用之Future模式

上圖展示的是Future模式流程圖,在廣義的Future模式中,雖然獲取數據是一個耗時的操作,但是服務程序不等數據完成就立即返回客戶端一個偽造的數據(就是上述說的“契約”),實現了Future模式的客戶端並不急於對其進行處理,而是先去處理其他業務,充分利用了等待的時間,這也是Future模式的核心所在,在完成了其他數據無關的任務之後,最後在使用返回比較慢的Future數據。這樣在整個調用的過程中就不會出現長時間的等待,充分利用時間,從而提高系統效率。

1、Future主要角色

多線程異步調用之Future模式

2、Future的核心結構圖如下:

多線程異步調用之Future模式

上述的流程就是說:Data為核心接口,這是客戶端希望獲取的數據,在Future模式中,這個Data接口有兩個重要的實現,分別是:RealData和FutureData。RealData就是真實的數據,FutureData他是用來提取RealData真是數據的接口實現,用於立即返回得到的,他實際上是真實數據RealData的代理,封裝了獲取RealData的等待過程。

說了這些理論的東西,倒不如直接看代碼來的直接些,請看代碼!

三、Future模式的簡單實現

主要包含以下5個類,對應著Future模式的主要角色:

多線程異步調用之Future模式

1、Data接口

/**
 * 返回數據的接口
 */
public interface Data {
 String getResult();
}

2、FutureData代碼

/**
 * Future數據,構造很快,但是是一個虛擬的數據,需要裝配RealData
 */
public class FutureData implements Data {
 private RealData realData = null;
 private boolean isReady = false;
 private ReentrantLock lock = new ReentrantLock();
 private Condition condition = lock.newCondition();
 @Override
 public String getResult() {
 while (!isReady) {
 try {
 lock.lock();
 condition.await();
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 lock.unlock();
 }
 }
 return realData.getResult();
 }
 public void setRealData(RealData realData) {
 lock.lock();
 if (isReady) {
 return;
 }
 this.realData = realData;
 isReady = true;
 condition.signal();
 lock.unlock();
 }
}

3、RealData代碼

public class RealData implements Data {
 private String result;
 public RealData(String param) {
 StringBuffer sb = new StringBuffer();
 sb.append(param);
 try {
 //模擬構造真實數據的耗時操作
 Thread.sleep(5000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 result = sb.toString();
 }
 @Override
 public String getResult() {
 return result;
 }
}

4、Client代碼

public class Client {
 public Data request(String param) {
 //立即返回FutureData
 FutureData futureData = new FutureData();
 //開啟ClientThread線程裝配RealData
 new Thread(() -> {
 {
 //裝配RealData
 RealData realData = new RealData(param);
 futureData.setRealData(realData);
 }
 }).start();
 return futureData;
 }
}

5、Main

/**
 * 系統啟動,調用Client發出請求
 */
public class Main {
 public static void main(String[] args) {
 Client client = new Client();
 Data data = client.request("Hello Future!");
 System.out.println("請求完畢!");
 try {
 //模擬處理其他業務
 Thread.sleep(2000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("真實數據:" + data.getResult());
 }
}

6、執行結果:

多線程異步調用之Future模式

四、JDK中的Future模式實現

上述實現了一個簡單的Future模式的實現,因為這是一個很常用的模式,在JDK中也給我們提供了對應的方法和接口,先看一下實例:

public class RealData implements Callable {
 private String result;
 public RealData(String result) {
 this.result = result;
 }
 @Override
 public String call() throws Exception {
 StringBuffer sb = new StringBuffer();
 sb.append(result);
 //模擬耗時的構造數據過程
 Thread.sleep(5000);
 return sb.toString();
 }
}

這裡的RealData 實現了Callable接口,重寫了call方法,在call方法裡邊實現了構造真實數據耗時的操作。

public class FutureMain {
 public static void main(String[] args) throws ExecutionException, InterruptedException {
 FutureTask futureTask = new FutureTask<>(new RealData("Hello"));
 ExecutorService executorService = Executors.newFixedThreadPool(1);
 executorService.execute(futureTask);
 System.out.println("請求完畢!");
 try {
 Thread.sleep(2000);
 System.out.println("這裡經過了一個2秒的操作!");
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("真實數據:" + futureTask.get());
 executorService.shutdown();
 }
}

執行結果:

多線程異步調用之Future模式

上述代碼,通過:FutureTask futureTask = new FutureTask<>(new RealData("Hello")); 這一行構造了一個futureTask 對象,表示這個任務是有返回值的,返回類型為String,下邊看一下FutureTask的類圖關係:

多線程異步調用之Future模式

FutureTask實現了RunnableFuture接口,RunnableFuture接口繼承了Future和Runnable接口。因為RunnableFuture實現了Runnable接口,因此FutureTask可以提交給Executor進行執行,FutureTask有兩個構造方法,如下:

構造方法1,參數為Callable:

多線程異步調用之Future模式

構造方法2,參數為Runnable:

多線程異步調用之Future模式

上述的第二個構造方法,傳入的是Runnable接口的話,會通過Executors.callable()方法轉化為Callable,適配過程如下:

多線程異步調用之Future模式

多線程異步調用之Future模式

這裡為什麼要將Runnable轉化為Callable哪?首先看一下兩者之間的區別:

(1) Callable規定的方法是call(),Runnable規定的方法是run();

(2) Callable的任務執行後可返回值,而Runnable的任務是不能返回值得;

(3) call()方法可以拋出異常,run()方法不可以;

(4) 運行Callable任務可以拿到一個Future對象,Future 表示異步計算的結果。

最關鍵的是第二點,就是Callable具有返回值,而Runnable沒有返回值。Callable提供了檢查計算是否完成的方法,以等待計算的完成,並獲取計算的結果。

計算完成後只能使用 get 方法來獲取結果,如果線程沒有執行完,Future.get()方法可能會阻塞當前線程的執行;如果線程出現異常,Future.get()會throws InterruptedException或者ExecutionException;如果線程已經取消,會拋出CancellationException。取消由cancel 方法來執行。isDone確定任務是正常完成還是被取消了。

一旦計算完成,就不能再取消計算。如果為了可取消性而使用 Future 但又不提供可用的結果,則可以聲明Future> 形式類型、並返回 null 作為底層任務的結果。


分享到:


相關文章: