Future 設計模式核心原理圖:
client端通過 FutureData 發送一個執行耗時操作的請求,FutureData 則直接返回一個回調接口的引用(Data 接口,用於返回獲取到的真正結果),然後在 FutureData 內部再另起一個線程去執行真正的耗時操作。當 client 端 執行了獲取結果的方法時,如果執行完成,則返回結果。如果還在執行中,則會進入線程等待狀態,一直等到執行完成進行線程喚醒之後才能拿到結果。
這個設計模式在 Java JDK 中已經被實現了,這裏先看一下自測代碼,從最裏層開始看:
自測 Future 模式實現
- RealData 實現類
public class RealData {
private String data;
public RealData(String data) throws InterruptedException {
System.out.println("模擬數據加載中。。。");
TimeUnit.SECONDS.sleep(5);
this.data = data;
}
public String getResult() {
return data + " - real data";
}
}
上面流程圖中標註的 RealData 也要實現 Data 接口,這裏自己實現的話,其實也是可以省略的,自己寫一個獲取方法,效果也是一樣的。這裏模擬的是執行耗時操作。
- FutureData 實現類
public class FutureData implements Data {
private boolean isReady;// 是否準備好了。 也就是耗時操作是否執行完成了。
private RealData data;// 真實數據操作對象
public synchronized void setRequest(RealData data) {
if (isReady) {
return;
}
this.data = data;
isReady = true;
notify();
}
@Override
public synchronized String getResult() throws InterruptedException {
if (!isReady) {
System.out.println("等待數據返回中。。。");
wait();
}
return data.getResult();
}
}
這裏說一下,因爲 RealData 的耗時操作直接在構造方法中寫的,所以,當進入 setRequest 方法的時候,說明 RealData 已經實例化好了,也就是耗時操作已經執行完成了。這裏 FutureData 也就是 RealData 的一個代理,包裝着 RealData 的耗時操作過程。
wait() 和 notify() 方法 一定要配合 synchronize 關鍵字使用。
- Client 端實現
public class Client {
public Data setRequest(String requestString) {
FutureData data = new FutureData();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("client run : " + System.currentTimeMillis());
data.setRequest(new RealData(requestString));// 當 RealData 的構造方法執行完成之後,纔會進入到 setRequest的方法中。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
return data;
}
}
- 調用代碼
public class Main {
/**
* Futrue 設計模式,
* client端 發送某個耗時請求 - > FutrueData 代理對象 先返回一個 假對象,然後其內部開啓線程進行真實請求操作 -- > RealData 真實處理對象,處理結束之後,回調數據給 代理對象,代理對象再進行通知 client端
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
Client client = new Client();
Data data = client.setRequest("this is test");
System.out.println("main : " + System.currentTimeMillis());
System.out.println(data.getResult()); // 會進入線程等待狀態。
}
}
JDK 內部 Future 實現
上面也說了,這個設計模式 JDK 內部是已經實現了這個機制。 FutureTask 作爲我們的代理類,它實現了 Runnable 接口,本身就作爲一個獨立的線程執行,但是它沒有start 方法,必須配合線程池使用。因爲 FutureTask 的傳入對象是 Callable(接口) 類型,所以,我們的 RealData 類也需要實現 Callable 接口。真實數據最終會通過 Callable 接口的 call() 方法傳遞給 FutureTask 。
- 實現代碼:
public class RealData_1 implements Callable<String> {
private String data;
public RealData_1(String data) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("real data 1");
this.data = data;
}
@Override
public String call() throws Exception {
System.out.println("real data 1 call()");
return data;
}
}
- 調用方式:
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Client client = new Client();
// Data data = client.setRequest("this is test");
// System.out.println("main : " + System.currentTimeMillis());
// System.out.println(data.getResult());
/**
* Java jdk 內置 FutureTask 是一個線程類,必須使用配合線程池調用。
*/
FutureTask futureTask = new FutureTask(new RealData_1("this is test jdk future"));
ExecutorService exe = Executors.newFixedThreadPool(1);
exe.submit(futureTask);
System.out.println(futureTask.get());
}
最後說一下線程池的 submit 方法和 execute 方法的區別:
- submit 可以傳入實現 Callable接口的實例對象。
- submit 方法有返回值。