1. 前言
在併發程式設計中,非同步回撥的效率不言而喻,在業務開發中,如果由阻塞的任務需要執行,必然要使用非同步執行緒。並且,如果我們想在非同步執行之後,根據他的結果執行一些動作。
JDK 8 之前的 Future 只能解決上面需求的一半問題,即非同步執行,返回一個 Future,需要程式設計師呼叫 get 方法等待,或者使用 isDone 輪詢。
效率不高。
JDK 8 新出的 CompletableFuture API 可以解決這個問題。但他的 API, 說實話,不太好用。
我們只想要一個簡單的 API,能實現我們的回撥功能。
我需要 3 個功能:
- 能通過 get 之類的方法返回結果。
- 能設定監聽器進行回撥。
- 可以在業務執行緒中設定成功或者失敗。
樓主寫一個簡單的例子,借鑑了 Netty 的非同步 API,希望能起到拋磚引玉的作用。
2. 設計
根據我們的需求: 第一,我們需要一個類,擁有 get 方法和 addListener 方法。 第二,我們需要一個類,能夠回撥我們設定的監聽器。 第三,我們需要一個類,能夠在業務執行緒中設定成功或者失敗。
3. 初步實現
設計一個監聽器介面:
/**
* 監聽器
* @author stateis0
*/
public interface MyListener {
/**
* 子類需要重寫此方法,在非同步任務完成之後會回撥此方法。
* @param promise 非同步結果佔位符。
*/
void operationComplete(MyPromise promise);
}
複製程式碼
設計一個非同步佔位符,類似 Future:
/**
* 非同步執行結果佔位符
*
* @author stateis0
*/
public class MyPromise {
/** 監聽器集合*/
List<MyListener> listeners = new ArrayList<MyListener>();
/** 是否成功*/
boolean success;
/** 執行結果**/
Object result;
/** 設定事變計數器**/
int failCount;
/**
* 設定成功,並通知所有監聽器。
* @param result 結果
* @return 是否成功
*/
public boolean setSuccess(Object result) {
if (success) {
return false;
}
success = true;
this.result = result;
signalListeners();
return true;
}
/**
* 通知所有監聽器,回撥監聽器方法。
*/
private void signalListeners() {
for (MyListener l : listeners) {
l.operationComplete(this);
}
}
/**
* 設定失敗
* @param e 異常物件
* @return 設定是否成功
*/
public boolean setFail(Exception e) {
if (failCount > 0) {
return false;
}
++failCount;
result = e;
signalListeners();
return true;
}
/**
* 是否成功執行
*/
public boolean isSuccess() {
return success;
}
/**
* 新增監聽器
* @param myListener 監聽器
*/
public void addListener(MyListener myListener) {
listeners.add(myListener);
}
/**
* 刪除監聽器
* @param myListener 監聽器
*/
public void removeListener(MyListener myListener) {
listeners.remove(myListener);
}
/**
* 獲取執行結果
*/
public Object get() {
return result;
}
}
複製程式碼
我們希望使用執行緒池執行此類任務,所以需要一個自定義的 Runnable,而在這個 Runnable 中,我們需要做一些簡單的手腳:
/**
* 一個任務類,通過重寫 doWork 方法執行任務
* @param <V> 返回值型別
* @author stateis0
*/
public abstract class MyRunnable<V> implements Runnable {
final MyPromise myPromise;
protected MyRunnable(MyPromise myPromise) {
this.myPromise = myPromise;
}
@Override
public void run() {
try {
V v = doWork();
myPromise.setSuccess(v);
} catch (Exception e) {
myPromise.setFail(e);
}
}
/**
* 子類需要重寫此方法。並返回值,這個值由 Promise 的 get 方法返回。
*/
public abstract V doWork();
}
複製程式碼
4. 寫個 Demo 測試一下
/**
* @author stateis0
*/
public class MyDemo {
public static void main(String[] args) {
// 佔位物件
final MyPromise myPromise = new MyPromise();
final Dto dto = new Dto();
// 執行緒池
Executor executor = Executors.newFixedThreadPool(1);
// 非同步執行任務,
executor.execute(new MyRunnable<String>(myPromise) {
@Override
public String doWork() {
return dto.doSomething();
}
});
// 新增一個監聽器
myPromise.addListener(new MyListener() {
// 當任務完成後,就執行此方法。
@Override
public void operationComplete(MyPromise promise) {
// 獲取結果
String result;
// 如果任務成功執行了
if (promise.isSuccess()) {
// 獲取結果並列印
result = (String) promise.get();
System.out.println("operationComplete ----> " + result);
}
// 如果失敗了, 列印異常堆疊
else {
((Exception) promise.get()).printStackTrace();
}
}
});
}
}
class Dto {
public String doSomething() {
System.out.println("doSomething");
// throw new RuntimeException("cxs");
return "result is success";
}
}
複製程式碼
執行結果:
doSomething
operationComplete ----> result is success
複製程式碼
符合我們的預期。我們希望在業務物件 Dto 的 doSomething 成功返回之後,回撥監聽器的 operationComplete 方法。如果失敗,列印異常堆疊。
當然,整體程式碼比較簡單,僅僅只是拋磚引玉。
實際上,如果直接向 Callable 或者 Runnable 傳入一個業務物件,當 call 方法或者 run 方法執行完畢,就可以根據執行結果執行我們的業務物件的方法了。這樣就是一個最簡單直接的非同步回撥。
只是這樣過於耦合。
非同步任務和業務的任務耦合在了一起,並且不能新增多個監聽器,也無法使用 promise 的 setSuccess 功能和 setFail 功能,這兩個功能可以在業務執行緒中設定成功或者失敗,靈活性更高。
關於非同步,我們可以多看看 Netty 的 API 設計,易懂好用。