基于JAVA的Promise模式實現

CarPost 8年前發布 | 19K 次閱讀 Java開發

來自: http://my.oschina.net/andylucc/blog/608499


Promise模式是一種異步編程模式,即我們可以先開始一個任務,拿到這個任務的憑據而并不需要立即得到這個任務的執行結果才繼續往下執行,我們拿著這個憑證可以在之后任何需要的時候去兌換結果。這篇文章主要介紹一種基于JAVA的Promise模式實現并結合一些例子。


原始實現

為了能夠讓大家對這個模式有個印象,我舉個簡單點的例子,假如我們正在做百度百科這個頁面,我們需要給前端提供一個服務(下面的代碼我們將結果打印出來來模擬),可以根據id獲取百科條目的內容,具體的例子是我們想要獲取某個明星的百度百科信息。而這個明星的信息有兩個相關內容可能需要調用別人的服務來獲取,一個是獲取明星相關的人物信息;另一個是獲取這個明星相關的新聞。我們假設這兩個服務分別位于不同的業務部門,而且由于業務的復雜性,服務比較慢,我用下面的代碼來表示:

/**
 * @author float.lu
 */
public class OldLongCallExample {
    public static void main (String ...s) {
        long start = System.nanoTime();
        //280ms
        String result1 = getRelatedRoles();
        //250ms
        String result2 = getRelatedNews();
        System.out.println("Result:" + result1 + result2);
        System.out.println("take:" + TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - start)) + "ms");
    }

上面的代碼我們可以看出,我們首先調用getRelatedRoles獲取明星相關人物,這里需要花費280ms,拿到明星相關人物之后我們繼續調用getRelatedNew獲取明星相關新聞,這里需要250ms,我們總共需要約530ms,這個時間是很長的。


思考

其實對于上面的這種情況,我們在調用getRelatedRoles之后其實并不需要等待getRelatedRoles給出結果之后在往下進行,因為后面花費時間較長的部分getRelatedNews并不依賴于getRelatedRoles的結果,因此我們順利成章的認為我們可以在調用getRelatedRoles之后不需要等待,我們繼續調用getRelatedNews,當主方法需要返回的時候我們再分別取拿getRelatedRoles和getRelatedNews的結果,這就是本文基于Promise模式的實現要解決的事情。


基于JPromise的實現

JPromise是基于JAVA的Promise的實現,在抽象類Promise中我們主要提供了三個抽象方法:

/**
 * 獲取執行結果
 * @return
 */
public abstract V get();

/**
 * 帶超時時間的獲取執行結果
 * @param timeout
 * @param unit
 * @return
 */
public abstract V get(long timeout, TimeUnit unit);

/**
 * 獲取異常信息
 * @return
 */
public abstract Throwable getException();

和一個將普通代碼邏輯包裝成Promise的工具方法:

/**
 * 將普通邏輯包裝成Promise
 * @param task
 * @param <V>
 * @return
 */
public static <V> Promise<V> wrap(final AsyncTask<V> task){
    if (executorService == null) {
        setDefaultExecutorService();
    }
    return new DefaultPromise<V>(executorService.submit(new Callable<V>() {
        @Override
        public V call() throws Exception {
            return task.call();
        }
    }));
}

我們通過JPromise可以將不同的業務邏輯包裝成Promise對象,我們把Promise對象稱作憑證,通過Promise抽象類提供的get或get(timeout,unit)方法來在需要的時候根據憑證獲取結果,同時允許超時。那么最開始的實現可以改造成下面的樣子:

/**
 * @author float.lu
 */
public class PromiseLongCallExample {
    public static void main (String ...s) {
        long start = System.nanoTime();
        //提交任務不需要立即拿到結果
        Promise<String> resolve1 = Promise.wrap(new AsyncTask<String>() {
            @Override
            public String call() throws Exception {
                return getRelatedRoles();
            }
        });
        //提交任務不需要立即拿到結果
        Promise<String> resolve2 = Promise.wrap(new AsyncTask<String>() {
            @Override
            public String call() throws Exception {
                return getRelatedNews();
            }
        });

        System.out.println("Result:" + resolve1.get() + resolve2.get());
        System.out.println("take:" + TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - start)) + "ms");
    }

經過測試,上面的代碼執行時間約為280ms,相比最開始的530ms得到了很大的優化。


異常捕獲

通常異步執行的邏輯都會面臨著異常捕獲的問題,Jpromise的實現將邏輯異常捕獲并通過getException向外面提供訪問接口。


自定義線程池

基于JPromise的異步任務都是由額外的線程執行的,因此我們需要為JPromise配置線程池。JPromise支持用戶自定義線程池,包括基于Spring容器的配置,只需要將自定義的ExecutorService注入到PromiseFigure的executorService屬性即可。


總結

Promise模式的適用場景也是有限的,當下層邏輯不依賴于上層邏輯,這些邏輯通常是串行執行的時候可以使用Promise模式來進行優化,優化效果還是很顯著的。


項目地址:https://git.oschina.net/floatlu/jpromise

 本文由用戶 CarPost 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!