Future模式

Java多線程編程中,常用的多線程設計模式包括:Future模式、Master-Worker模式、Guarded Suspeionsion模式、不變模式和生產者-消費者模式等。

1、Future模式核心思想

Future模式的核心在於:去除了主函數的等待時間,並使得原本需要等待的時間段可以用於處理其他業務邏輯(根據《Java程序性能優化》)。

Future模式有點類似於商品訂單。在網上購物時,提交訂單後,在收貨的這段時間裏無需一直在家裏等候,可以先幹別的事情。類推到程序設計中時,當提交請求時,期望得到答覆時,如果這個答覆可能很慢。傳統的時一直等待到這個答覆收到時再去做別的事情,但如果利用Future設計模式就無需等待答覆的到來,在等待答覆的過程中可以幹其他事情。

例如如下的請求調用過程時序圖。當call請求發出時,需要很長的時間才能返回。Client需要一直等待,等返回數據後才能繼續其他操作;

Created with Raphaël 2.1.0ClientClientServerServerDataDatacall()other_call()other_call_returncall_returnother_jobother_job

而如下Future模式的時序圖中客戶端則無需等到可以做其他的事情。服務器段接收到請求後立即返回結果給客戶端,這個結果並不是真實的結果(是虛擬的結果),也就是先獲得一個假數據,然後執行其他操作。

Created with Raphaël 2.1.0ClientClientServerServerDataDatacall()other_call()call_returnother_jobother_jobother_call_returncall_return

2、Future模式Java實現

Client的實現

Client主要完成的功能包括:
1. 返回一個FutureData;
2. 開啓一個線程用於構造RealData。

public class Client {
    public Data request(final String string) {
        final FutureData futureData = new FutureData();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //RealData的構建很慢,所以放在單獨的線程中運行
                RealData realData = new RealData(string);
                futureData.setRealData(realData);
            }
        }).start();

        return futureData; //先直接返回FutureData
    }
}

Data的實現

無論是FutureData還是RealData都實現該接口。

public interface Data {
    String getResult() throws InterruptedException;
}

FutureData的實現

FutureData是Future模式的關鍵,它實際上是真實數據RealData的代理,封裝了獲取RealData的等待過程。

//FutureData是Future模式的關鍵,它實際上是真實數據RealData的代理,封裝了獲取RealData的等待過程
public class FutureData implements Data {
    RealData realData = null; //FutureData是RealData的封裝
    boolean isReady = false;  //是否已經準備好

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

    @Override
    public synchronized String getResult() throws InterruptedException {
        if(!isReady) {
            wait(); //一直等到RealData注入到FutureData中
        }
        return realData.getResult();
    }
}

RealData的實現

RealData是最終需要使用的數據,它的構造函數很慢。

public class RealData implements Data {
    protected String data;

    public RealData(String data) {
        //利用sleep方法來表示RealData構造過程是非常緩慢的
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.data = data;
    }

    @Override
    public String getResult() {
        return data;
    }
}

測試運行

主函數主要負責調用Client發起請求,並使用返回的數據。

public class Application {
    public static void main(String[] args) throws InterruptedException {
        Client client = new Client();
        //這裏會立即返回,因爲獲取的是FutureData,而非RealData
        Data data = client.request("name");
        //這裏可以用一個sleep代替對其他業務邏輯的處理
        //在處理這些業務邏輯過程中,RealData也正在創建,從而充分了利用等待時間
        Thread.sleep(2000);
        //使用真實數據
        System.out.println("數據="+data.getResult());
    }
}

3、Future模式的JDK內置實現

由於Future是非常常用的多線程設計模式,因此在JDK中內置了Future模式的實現。這些類在java.util.concurrent包裏面。其中最爲重要的是FutureTask類,它實現了Runnable接口,作爲單獨的線程運行。在其run()方法中,通過Sync內部類調用Callable接口,並維護Callable接口的返回對象。當使用FutureTask.get()方法時,將返回Callable接口的返回對象。同樣,針對上述的實例,如果使用JDK自帶的實現,則需要作如下調整。

首先,Data接口和FutureData就不需要了,JDK幫我們實現了。

其次,RealData改爲這樣:

import java.util.concurrent.Callable;

public class RealData implements Callable<string> {
    protected String data;

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

    @Override
    public String call() throws Exception {
        //利用sleep方法來表示真是業務是非常緩慢的
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return data;
    }
}

最後,在測試運行時,這樣調用:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class Application {
    public static void main(String[] args) throws Exception {
        FutureTask<string> futureTask =
                new FutureTask<string>(new RealData("name"));
        ExecutorService executor =
                Executors.newFixedThreadPool(1); //使用線程池
        //執行FutureTask,相當於上例中的client.request("name")發送請求
        executor.submit(futureTask);
        //這裏可以用一個sleep代替對其他業務邏輯的處理
        //在處理這些業務邏輯過程中,RealData也正在創建,從而充分了利用等待時間
        Thread.sleep(2000);
        //使用真實數據
        //如果call()沒有執行完成依然會等待
        System.out.println("數據=" + futureTask.get());
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章