Java並行程序設計模式——Future模式

問題引出

現在我們想要炒一道菜,但是我們沒有廚具和菜,現在我們從網上訂購了一套廚具,但在廚具送來的期間,我們不必一直等到廚具到來,而是可以先去買菜,然後廚具到了之後直接開始炒菜

這就是Future模式,在程序設計中,當某一段程序提交了一個請求,期望得到一個答覆。但非常不幸的是,服務程序對這個請求的處理可能很慢,比如這個請求可能是通過互聯網、HTTP或者Web Service等並不太高效的方式調用的。在傳統的單線程模式下,調用函數是同步的,也就是說他必須等到服務程序返回結果後,才能進行其他處理而在Future模式下,調用方式改爲異步,而原先等待返回的時間段,在主調用函數中,則可用於處理其他事務

傳統模式與Future模式流程對比

在這裏插入圖片描述在這裏插入圖片描述
可以看出,Future模式中,雖然call本身仍然需要很長一段時間來處理程序。但是,服務程序不等數據處理完成便立即返回客戶端一個僞造的數據(相當於商品訂單,而不是商品本身)。
實現了Future模式的客戶端在拿到這個返回結果後,並不着急對其進行處理,而去調用了其他業務邏輯,充分利用了等待時間。在完成了其他業務邏輯後,最後再使用返回比較慢的Future數據。這樣,在整個調用過程中,就不存在無謂的等待,充分利用了所有的時間片段,從而提高系統的響應速度

Future模式的代碼實現

在這裏插入圖片描述

參與者 作用
Main 系統啓動,調用Client發送請求,並使用返回數據
Client 返回Data對象,立即返回FutureData,並開啓ClientThread線程裝配RealData
Data 返回數據的接口
FutureData Future數據,構造很快,但是是一個虛擬數據,需要裝配RealData
RealData 真實數據,構造比較緩慢
public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        // 這裏會立刻返回,因爲得到的是FutureData而不是RealData
        Data data = client.request("name");
        System.out.println("請求完畢");
        try {
            // 這裏可以用一個sleep替代對其他業務邏輯的處理
            // 在處理這些業務邏輯的過程中,RealData被創建,從而充分利用了等待時間
            Thread.sleep(1000);
            System.out.println("其他操作");
        } catch (InterruptedException e) {

        }
        // 使用真實數據
        System.out.println("真實數據:" + data.getResult());
    }
}
public class Client {
    public Data request(final String queryStr) {
        final FutureData futureData = new FutureData();
        new Thread() {
            @Override
            public void run() {
                RealData realData = new RealData(queryStr);
                futureData.setRealData(realData);
            }
        }.start();
        return futureData;
    }
}
public interface Data {
    public String getResult();
}
// FutureData是Future模式的關鍵,它實際上是真實數據RealData的代理,封裝了獲取RealData的等待過程
public class FutureData implements Data {

    protected RealData realData = null;
    protected boolean isReady = false;

    public synchronized void setRealData(RealData realData) {
        if (isReady) {
            return;
        }
        this.realData = realData;
        this.isReady = true;
        notifyAll();        // RealData已經被注入,通知getResult()
    }

    @Override
    public synchronized String getResult() {    // 會等待RealData構造完成
        while (!isReady) {
            try {
                wait();             // 一直等待,直到RealData注入
            } catch (InterruptedException e) {

            }
        }
        return realData.result;        // 由RealData實現
    }
}
public class RealData implements Data {

    protected final String result;

    public RealData(String para) {
        // RealData構造可能會很慢,需要用戶等待很久,這裏使用sleep模擬
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            sb.append(para + " ");
            try {
                // 這裏使用sleep模擬,代替一個很慢的操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {

            }
        }
        result = sb.toString();
    }

    @Override
    public String getResult() {
        return result;
    }
}
// 運行結果
請求完畢
// 等待一秒後
其他操作
//(等待10秒)
真實數據:name name name name name name name name name name 

擴展

由於Future模式的常用性,以至於JDK的併發包中就已經內置了一種Future模式的實現。JDK中的實現是相當複雜的,並提供了更爲豐富的線程控制功能,但其中的基本用意和核心概念是完全一致的

在這裏插入圖片描述使用JDK內置的Future模式

public class RealData implements Callable<String> {
    private String para;

    public RealData(String para) {
        this.para = para;
    }

    @Override
    public String call() throws Exception {
    	// 這裏是真實的業務邏輯,其執行可能會很慢
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            sb.append(para + " ");
            try {
                // 這裏使用sleep模擬,代替一個很慢的操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {

            }
        }
        return sb.toString();
    }
}
// 由於使用了JDK內置的框架,Data接口,FutureData等對象就不再需要了
// 直接通過RealData構造FutureTask,並將其作爲單獨的線程運行。在提交結果後,執行其他業務邏輯,最後通過FutureTask.get()方法得到RealData的執行結果
public class Main {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 構造FutureTask
        FutureTask<String> futureTask = new FutureTask<String>(new RealData("a"));
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // 執行FutureTask,相當於上例中的client.request("a") 發送請求
        // 在這裏開啓線程進行RealData的call()執行
        executor.submit(futureTask);
        System.out.println("請求完畢");
        try {
            // 這裏依然可以做額外的數據操作,這裏使用sleep代替其他業務邏輯的處理
            Thread.sleep(1000);
            System.out.println("其他操作");
        } catch (InterruptedException e) {

        }
        // 相當於上例中的data,getResult(),取得call()方法的返回值
        // 如果此時call()方法沒有執行完成,則依然會等待
        System.out.println("真實數據:" + futureTask.get());
    }
}
// 結果
請求完畢
// 等待一秒後
其他操作
// 等待一段時間
真實數據:a a a a a a a a a a 
注:
  1. JDK內置的Future模式功能強大,除了基本的功能外,它還可以取消Future任務,或者設定Future任務的超時時間
  2. Future模式的核心在於去除了主函數中的等待時間,並使得原本需要等待的時間段可以用於處理其他業務邏輯,從而充分利用計算機資源
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章