## Future模式(異步獲取結果,自實現)
傳統串行流程如下:

在獲取數據時會阻塞等待,拿到數據后再執行其他的任務。
而`Future`模式會立即返回一個憑證(`Future`),這時可以執行其他任務;等需要數據再通過前面的`Future`憑證獲取數據即可,流程如下圖:

如下我們來自己實現一個簡單的Future模式:
首先分析設計如下對象:
* Main 系統啟動,調用Client發出請求,得到立即返回的FutureData
* Client 客戶端,發出獲取Data的請求,立即返回FutureData,并開啟線程裝配RealData
* Data 返回數據的接口
* FutureData 虛擬的數據,是一個憑證,需要裝配RealData
* RealData 真實數據,構建較慢
代碼如下:
Data接口:
~~~
public interface Data {
String getResult() throws InterruptedException;
}
~~~
RealData類:
~~~
public class RealData implements Data{
private String content;
public RealData(String content){
this.content = content;
}
@Override
public String getResult() {
return content;
}
}
~~~
FutureData類:
~~~
public class FutureData implements Data {
// 是否準備好
private boolean isReady;
// 組裝真實數據
private RealData realData;
@Override
public synchronized String getResult() {
// 沒有準備好,則阻塞等待
while (!isReady){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return realData.getResult();
}
public synchronized void setRealData(RealData realData){
if(isReady){
return;
}
this.realData = realData;
this.isReady = true;
// 通知其他線程
notifyAll();
}
}
~~~
Client類:
~~~
public class Client {
// 請求數據
public FutureData request(String queryStr){
FutureData futureData = new FutureData();
// 開啟線程異步出組裝真實數據
new Thread(()->{
// 耗時操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 構建真實數據
RealData realData = new RealData("hello future," + queryStr);
futureData.setRealData(realData);
}).start();
// 立即返回
return futureData;
}
}
~~~
Main程序類:
~~~
public class Main {
public static void main(String[] args) {
Client client = new Client();
FutureData futureData = client.request("測試");
// 做其他的事情
System.out.println("做其他事情1");
System.out.println("做其他事情2");
// 獲取結果
String result = futureData.getResult();
System.out.println("異步結果為:"+result);
}
}
~~~
```
輸出結果:
做其他事情1
做其他事情2
異步結果為:hello future,測試
```
## JDK中的Future模式
如下圖,是`JDK1.8`中封裝的`Future`模式實現。(1.5就又引入了`Future`模式,1.8的功能更加強大,提供了`CompletableFuture`)

其中:
* `類MyCallable`實現的`Callable`接口的`call()`方法會返回真實的數據(類似于自實現Future模式中的數據接口`Data`的`getResult()`方法)
* FutureTask類似于FutureData,都是用來當做異步調用里的立即返回的憑證
* 線程池類似于Client,都是用來執行任務的
如下是JDK的future的例子:
MyCallable類:
~~~
public class MyCallable implements Callable<String> {
private String str;
public MyCallable(String str){
this.str = str;
}
@Override
public String call() throws Exception {
// 模擬比較耗時的操作
Thread.sleep(2000);
return "hello jdk future," + str;
}
}
~~~
Main程序:
~~~
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
FutureTask<String> futureTask = new FutureTask<>(new MyCallable("測試"));
// 提交任務
executor.submit(futureTask);
System.out.println("處理其他邏輯1");
System.out.println("處理其他邏輯2");
String result = futureTask.get();
System.out.println("(jdk)異步處理結果為:"+result);
executor.shutdown();
}
}
~~~
```
數據結果:
處理其他邏輯1
處理其他邏輯2
(jdk)異步處理結果為:hello jdk future,測試
```
另外,JDK的Future模式的Future接口還提供了一些高級的功能。
```
boolean cancel(boolean mayInterruptIfRunning);// 取消任務
boolean isCanclled();// 是否已經取消
boolean isDone();// 是否已經完成
V get(long timeout,TimeUnit unit);// 超時時間內獲取結果
```
## Guava擴展Future模式
`JDK`的`Future`模式中,`future.get()`是阻塞的,不利于高并發開發。`Guava`增強了`Future`模式,增加了完成時的回調接口,使`future`完成時可以自動通知應用程序進行獲取處理。
對上面的程序改造為guava的設置回調函數的方式,代碼如下:
Main程序:
~~~
public class Main {
public static void main(String[] args){
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(4));
// 提交任務
ListenableFuture<String> future = executorService.submit(new MyCallable("測試"));
// 添加回調函數
future.addListener(()->{
String result = null;
try {
result = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("(guava)異步處理結果為:"+result);
},MoreExecutors.directExecutor());
System.out.println("處理其他邏輯1");
System.out.println("處理其他邏輯2");
executorService.shutdown();
}
}
~~~
輸出結果:
```
處理其他邏輯1
處理其他邏輯2
(guava)異步處理結果為:hello guava future,測試
```
## Netty擴展Future模式
netty中也提供了支持設置Future回調的擴展。
~~~
public class Main {
public static void main(String[] args) {
// 創建netty線程組
EventExecutorGroup group = new DefaultEventExecutorGroup(4);
// 提交任務
Future<String> future = group.submit(new MyCallable("測試"));
System.out.println("處理其他邏輯1");
System.out.println("處理其他邏輯2");
future.addListener(new FutureListener<String>(){
@Override
public void operationComplete(Future<String> future) throws Exception {
String result = future.get();
System.out.println("(netty)異步處理結果為:"+result);
}
});
group.shutdownGracefully();
}
}
~~~
## JDK8的CompletableFuture
`JDK8`中提供的`CompletableFuture`更加強大。
`CompletableFuture`實現了`CompletionStage`接口和`Future`接口,前者是對后者的一個擴展,增加了異步回調、流式處理、多個`Future`組合處理的能力,使`Java`在處理多任務的協同工作時更加順暢便利。
使用`CompletableFuture`改造支持異步回調方法,代碼如下:
MySupplier類:
~~~
public class MySupplier implements Supplier<String> {
private String str;
public MySupplier(String str){
this.str = str;
}
@Override
public String get() {
// 模擬比較耗時的操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello jdk CompletableFuture," + str;
}
}
~~~
Main程序:
~~~
public class Main {
public static void main(String[] args){
CompletableFuture<String> future = CompletableFuture.supplyAsync(new MySupplier("測試"),
Executors.newFixedThreadPool(1));
future.whenCompleteAsync((r,t)->{
System.out.println("(CompletableFuture)異步處理結果為:"+r);
});
System.out.println("處理其他邏輯1");
System.out.println("處理其他邏輯2");
}
}
~~~
輸出結果:
```
處理其他邏輯1
處理其他邏輯2
(CompletableFuture)異步處理結果為:hello jdk CompletableFuture,測試
```
## 總結
1. 在`JDK1.5`中提供了`Future`模式,獲取數據時阻塞的,所以其他框架(`guava`和`netty`)對`Future`模式做了擴展,支持了回調函數。
2. 在`JDK1.8`中提供了`CompletableFuture`,支持了更加強大的異步回調、流式處理、多個`Future`組合處理的能力。
## 參考資料
* 書籍 葛一鳴 * 《Java高并發程序設計》
- 面試突擊
- Java虛擬機
- 認識字節碼
- 000Java發展歷史
- 000Macos10.15.7上編譯OpenJDK8u
- 001熟悉Java內存區域
- 002熟悉HotSpot中的對象
- 003Java如何計算對象大小
- 004垃圾判定算法與4大引用
- 005回收堆和方法區中對象
- 006垃圾收集算法
- 007HotSpot虛擬機垃圾算法實現篇1
- 007HotSpot虛擬機垃圾算法實現篇2
- 007HotSpot虛擬機垃圾算法實現篇3
- 008垃圾收集器
- 009內存分配與回收策略
- 010Java虛擬機相關工具
- 011調優案例分析
- 012一次IDEA的啟動速度調優
- 013類文件Class的結構
- 014熟悉字節碼指令
- 015類加載機制(過程)
- 016類加載器
- IDEA的JVM參數
- Java基礎
- Java自動裝箱與拆箱
- Java基礎數據類型
- Java方法的參數傳遞
- Java并發
- 001走入并行的世界
- 002并行程序基礎
- 003熟悉Java內存模型JMM
- 004Java并發之volatile關鍵字
- 005線程池入門到精通
- 006Java多線程間的同步控制方法
- 007Java維基準測試框架JMH
- 008Java并發容器
- 009Java的線程實現
- 010Java關鍵字synchronized
- 011一些并行模式的熟悉
- 單例模式和不變模式
- 生產者消費者模式
- Future模式
- 012一些并行算法的熟悉
- 面試總結
- 長亮一面