1. 程式人生 > >併發模式(一)Future模式

併發模式(一)Future模式

並行設計模式是對一些常用的多執行緒結構的總結和抽象,與序列程式設計相比,並行程式更復雜。

前言

常用的併發設計模式有Future模式、Master-Worker模式、Guarded Suspension模式、不變模式、生產者-消費者模式,在多執行緒環境中,合理使用模式,可以提高程式效能,優化程式設計。

接下來會記錄這些模式的學習過程,一一成文,以便以後查閱和複習。

不變模式

不變模式的實現很簡單,這裡說明一下。不變模式天生就是多執行緒友好的。一個物件一旦被建立,則它的內部狀態將永遠不會發生改變,沒有一個執行緒可以修改其內部狀態和資料,同時內部狀態也不會自行發生改變。基於這些特性,不變模式的物件,在多執行緒環境中不需要同步控制。

主要使用場景:

  1. 當物件建立後,其內部狀態和資料不再發生任何變化;
  2. 物件需要被共享、被多執行緒頻繁訪問。

程式碼實現:

  1. final修飾類,確保不能被繼承,沒有子類;
  2. 所有屬性私有化,並用final標記,確保不會被修改;
  3. 移除setter方法和其他所有修改自身屬性的方法;
  4. 建構函式的引數包括所有的屬性,提供賦值的視窗。

在JDK中,所有基本型別的包裝類、String都是使用不變模式實現的。

Future模式

概念

Future模式是多執行緒設計常用的一種設計模式,類似商品訂單。商品下單後,會立即得到下單成功的通知,客戶不用等待後續商家的操作,只等配送到家即可,下單後到收到商品這段時間,客戶可以做其他事情,不用在家等著商品。Future模式也類似Ajax的非同步請求,不用等待處理結果。

處理流程

傳統處理流程

客戶端發出call請求,這個請求需要很長一段時間才能返回。客戶端一直等待著,直到資料返回,隨後進行其他業務處理。

這裡寫圖片描述

Future模式流程

服務程式不需要等待資料處理完成便立即返回客戶端偽造的資料(相當於商品的訂單,而不是實際商品),客戶端拿到這個返回結果後,並不急於對其處理,而是利用等待的時間,呼叫其他業務邏輯。這就是Future模式的核心所在。

這裡寫圖片描述

主要參與者

Main:系統啟動,呼叫client發出請求。
Client:返回Data物件,立即返回FutureData,並開啟ClientThread執行緒裝配RealData。
Data:返回資料的介面。
FutureData:Future資料,構造很快,但是是一個虛擬的資料,需要裝配RealData。
RealData:真實資料,其構造過程是比較慢的。

程式碼實現

Future模式結構圖

這裡寫圖片描述

Main

//Future的簡單實現。
// 系統啟動,呼叫client發出請求
public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        //這裡會立即返回,因為得到的是FutureData,而不是RealData
        Data data = client.request("name");
        System.out.println("傳送請求完畢。。。");
        try {
            //模擬其他業務邏輯處理
            //處理過程中,RealData被建立,充分利用了等待的時間。
            System.out.println("Main 正在呼叫其他業務邏輯。。。");
            Thread.sleep(1000);
            System.out.println("Main 其他業務處理完成。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //使用真實的資料
        System.out.println("資料= " + data.getResult());
    }
}

Client

//返回Data物件,立即返回FutureData,並開啟ClientThread執行緒裝配RealData
public class Client {
    public Data request(final String requestStr){
        final FutureData future=new FutureData();
        //RealData構建過程很慢,所以在單獨的執行緒中進行
        new Thread(){
            @Override
            public void run() {
                RealData realData=new RealData(requestStr);
                future.setRealData(realData);
            }
        }.start();
        return future;//future會被立即返回
    }
}

Data

//返回資料的介面
public interface Data {
    public String getResult();
}

FutureData

//Future資料,構造很快,但是是一個虛擬的資料,需要裝配RealData
//FutureData是Future模式的核心,是RealData的真實代理,封裝了等待RealData的過程。
public class FutureData implements Data {
    protected RealData realData=null;//FutureData是RealData的包裝
    protected boolean isReady=false;

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


    @Override
    public synchronized String getResult() {
        //等待RealData構造完成
        while (!isReady){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return realData.result;//由RealData實現
    }
}

RealData

//真是資料,其構造過程是比較慢的
public class RealData implements Data {
    protected final String result;
    public RealData(String para){
        StringBuffer buffer=new StringBuffer();
        System.out.println("RealData 正在構造真實資料。。。");
        for (int i=0;i<10;i++){
            buffer.append(para);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
       System.out.println("RealData 真實資料構造完成。。。");
        result=buffer.toString();
    }
    @Override
    public String getResult() {
        return result;
    }
}

FutureData其實是RealData的一個代理,實現RealData的延遲效果。

JDK的內建實現

JDK內建的Future模式

這裡寫圖片描述

程式碼優化

使用JDK內建的Future模式優化程式碼,首先要實現Callable這個介面,DataFutureDataClient物件就不需要了。

RealData

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

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

    //call中寫具體的業務邏輯
    @Override
    public String call() throws Exception {
        //模擬真實業務邏輯,執行很慢
        StringBuffer buffer=new StringBuffer();
        System.out.println("RealData 正在構造真實資料。。。");
        for (int i=0;i<10;i++){
            buffer.append(para);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("RealData 真實資料構造完成。。。");
        return buffer.toString();
    }
}

Main

在Main方法中,直接通過RealData構造FutureTask,作為單獨的執行緒執行。

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //構造FutureTask
        FutureTask<String> future=new FutureTask<String>(new RealData("name"));
        ExecutorService executor= Executors.newFixedThreadPool(1);
        //執行FutureTask,相當於client.request()傳送請求
        //開啟執行緒進行RealData的call()執行
        executor.submit(future);
        System.out.println("傳送請求完畢");

        try {
            System.out.println("Main 正在呼叫其他業務邏輯。。。");
            Thread.sleep(2000);
            System.out.println("Main 其他業務處理完成。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //使用真實的資料
        //相當於data.getResult(),取得call()方法的返回值
        //如果call()方法沒有執行完成,則依然會等待
        System.out.println("資料= " + future.get());
    }
}

Future模式的核心在於除了主函式中的等待時間,並使得原本需要等待的時間可以用於其他業務邏輯的處理,充分利用了計算機資源,提高系統性能。

參考資料

葛一鳴:Java程式效能優化-讓你的Java程式更快、更穩定