## **主線程如何得知異步線程的執行結果?**
通過繼承Thread類或者實現Runnable接口方式創建的線程在執行后無法獲取執行結果,除非采用共享變量或者線程通信的方式獲得,但顯然這不是一種優雅的方式。Java中提供了使用Callable和Future來實現獲取任務結果的操作,Callable用來執行任務,產生結果,而Future用來獲得結果。
測試代碼
```
// 通過實現Callable接口創建線程任務
class Task implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("異步線程正在執行任務...");
Thread.sleep(3000);
return "3000";
}
}
class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 創建線程池
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(new Task());
System.out.println("主線程繼續執行");
// future獲取線程返回結果
String result = future.get();
System.out.println("主線程得到異步線程返回結果:" + result);
executor.shutdown();
}
}
```
運行結果
```
主線程繼續執行
異步線程正在執行任務...
異步線程返回結果:3000
```
## **如何實現Future功能**
我們來模擬一個在主線程中進行遠程**異步**請求并且**得到請求響應的數據**的功能
* 異步:那自然是需要在主線程中開啟一個線程
* 得到異步響應的數據:就是我們下面要探討實踐的
1.創建遠程請求的抽象類
```
public abstract class BaseRequest {
// 獲取請求結果
public abstract String getResponse();
}
```
2.創建實現具體請求的類
```
public class SendRequest extends BaseRequest {
// 最終返回的值
private String result;
// 構造函數模擬發送耗時3秒的遠程請求
public SendRequest(String params) {
System.out.println("正在讀取數據...");
...
Thread.sleep(3000);
...
System.out.println("讀取數據完成...");
result = "hello";
}
@Override
public String getResponse() {
return result;
}
}
```
3.創建請求future類,目前來看這個類只是對SendRequest的簡單封裝,暫且沒多大意義。
```
public class FutureRequest extends BaseRequest {
private SendRequest sendRequest;
public void sendRequest(SendRequest sendRequest) {
this.sendRequest = sendRequest;
}
@Override
public String getResponse() {
return sendRequest.getResponse();
}
}
```
4.測試
```
class test {
public static void main(String[] args) {
FutureRequest futureRequest = new FutureRequest();
new Thread(new Runnable() {
@Override
public void run() {
SendRequest sendRequest = new SendRequest("我是請求參數");
futureRequest.sendRequest(sendRequest);
}
}).start();
System.out.println("主線程繼續執行");
String result = futureRequest.getResponse();
System.out.println("主線程獲得異步請求結果:" + result);
}
}
```
執行結果
```
Exception in thread "main" java.lang.NullPointerException
at com.mask.FutureRequest.getResponse(FutureRequest.java:23)
at com.mask.test.main(ThreadDemo.java:98)
正在讀取數據...
讀取數據完成...
```
很顯然在獲取結果的時候報了空指針的異常,SendRequest 構造函數sleep了3秒,主線程繼續執行時FutureRequest 里的sendRequest對象還未創建成功,換種說法,主線程嘗試去獲取異步遠程請求的結果,但此時遠程請求還未得到響應,自然是獲取不得。那可如何是好呢?繼續改造代碼
改造第3步的FutureRequest類中方法,加入wait、notify方法
```
public class FutureRequest extends BaseRequest {
private SendRequest sendRequest;
// notify和wait 方法必須在 synchronized 中使用
public synchronized void sendRequest(SendRequest sendRequest) {
this.sendRequest = sendRequest;
notify();
}
@Override
public synchronized String getResponse() {
// 如果請求還沒響應時,主線程嘗試獲取結果會一直處于等待的狀態,
// 直到請求成功響應發送notify通知,等待才會解除
while (sendRequest == null) {
...
wait();
...
}
return sendRequest.getResponse();
}
}
```
運行結果
```
主線程繼續執行
異步任務正在讀取數據...
異步任務讀取數據完成...
主線程獲得異步請求結果:hello
```
執行結果正是我們預期的結果,測試代碼中主線程創建了線程去執行異步任務,但似乎代碼看起來并不是那么的優雅,理想的future客戶端應該是包含異步和獲取異步結果,所以我們繼續優化代碼再封裝一個future客戶端供主線程使用。
```
// Future客戶端
public class FutureClient {
public BaseRequest sendRequest(String params) {
FutureRequest futureRequest = new FutureRequest();
new Thread(new Runnable() {
@Override
public void run() {
SendRequest sendRequest = new SendRequest(params);
futureRequest.sendRequest(sendRequest);
}
}).start();
return futureRequest;
}
}
```
測試
```
class test {
public static void main(String[] args) {
FutureClient futureClient = new FutureClient();
BaseRequest baseRequest = futureClient.sendRequest("我是請求參數");
System.out.println("主線程繼續執行");
String result = baseRequest.getResponse();
System.out.println("主線程獲得異步請求結果:" + result);
}
}
```
執行結果
```
主線程繼續執行
異步任務正在讀取數據...
異步任務讀取數據完成...
主線程獲得異步請求結果:hello
```
完美