并行設(shè)計模式(一)-- Future模式

Java多線程編程中,常用的多線程設(shè)計模式包括:Future模式、Master-Worker模式、Guarded Suspeionsion模式、不變模式和生產(chǎn)者-消費者模式等。這篇文章主要講述Future模式,關(guān)于其他多線程設(shè)計模式的地址如下:
  關(guān)于Master-Worker模式的詳解: 并行設(shè)計模式(二)-- Master-Worker模式
  關(guān)于Guarded Suspeionsion模式的詳解: 并行設(shè)計模式(三)-- Guarded Suspeionsion模式
  關(guān)于不變模式的詳解: 并行設(shè)計模式(四)-- 不變模式
  關(guān)于生產(chǎn)者-消費者模式的詳解:并行設(shè)計模式(五)-- 生產(chǎn)者-消費者模式

  1. Future模式
    Future模式的核心在于:去除了主函數(shù)的等待時間,并使得原本需要等待的時間段可以用于處理其他業(yè)務(wù)邏輯。

Future模式有點類似于商品訂單。在網(wǎng)上購物時,提交訂單后,在收貨的這段時間里無需一直在家里等候,可以先干別的事情。類推到程序設(shè)計中時,當(dāng)提交請求時,期望得到答復(fù)時,如果這個答復(fù)可能很慢。傳統(tǒng)的是一直持續(xù)等待直到這個答復(fù)收到之后再去做別的事情,但如果利用Future模式,其調(diào)用方式改為異步,而原先等待返回的時間段,在主調(diào)用函數(shù)中,則可以用于處理其他事務(wù)。

例如如下的請求調(diào)用過程時序圖。當(dāng)call請求發(fā)出時,需要很長的時間才能返回。左邊的圖需要一直等待,等返回數(shù)據(jù)后才能繼續(xù)其他操作;而右邊的Future模式的圖中客戶端則無需等到可以做其他的事情。服務(wù)器段接收到請求后立即返回結(jié)果給客戶端,這個結(jié)果并不是真實的結(jié)果(是虛擬的結(jié)果),也就是先獲得一個假數(shù)據(jù),然后執(zhí)行其他操作。


圖片.png

Future模式的主要參與者如下表所示:


圖片.png
  1. Future模式的代碼實現(xiàn)


    圖片.png

    <1. Main函數(shù)的實現(xiàn)

Main函數(shù)主要負責(zé)調(diào)用Client發(fā)起請求,并使用返回的數(shù)據(jù):

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代替對其他業(yè)務(wù)邏輯的處理
            // 在處理這些業(yè)務(wù)邏輯過程中,RealData也正在創(chuàng)建,從而充分了利用等待時間
            Thread.sleep(2000);

            // 使用真實數(shù)據(jù)
            System.out.println("數(shù)據(jù)=" + data.getResult());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

<2. Client的實現(xiàn)

Client主要實現(xiàn)了獲取futrueData,開啟構(gòu)造RealData的線程,并在接受請求后,很快地返回FutureData

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

        new Thread(new Runnable() {
            @Override
            public void run() {
                // RealData的構(gòu)建很慢,所以放在單獨的線程中運行
                RealData realData = new RealData(string);
                futureData.setRealData(realData);
            }
        }).start();
        return futureData; // 先直接返回FutureData
    }
}

<3. Data的實現(xiàn)

Data是一個接口,提供了getResult()方法。無論futureData或者RealData都實現(xiàn)了這個接口

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

<4. FutureData的實現(xiàn)

FutureData實現(xiàn)了一個快速返回的RealData包裝。它只是一個包裝,或者說是一個RealData的虛擬實現(xiàn)。因此,它可以很快被構(gòu)造并返回。當(dāng)使用FutureData的getResult()方法是,程序會阻塞,等待RealData被注入到程序中,才使用RealData的getResult()方法返回。

public class FutureData implements Data {
    RealData realData = null; // FutureData是RealData的封裝
    boolean isReady = false; // 是否已經(jīng)準備好

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

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

<5. RealData的實現(xiàn)

RealData是最終需要使用的數(shù)據(jù)模型,它的構(gòu)造很慢。在這里,使用sleep()函數(shù)模擬這個過程

public class RealData implements Data {
    protected String data;

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

    @Override
    public String getResult() throws InterruptedException {
        return data;
    }
}
  1. JDK的內(nèi)置Future模式實現(xiàn)
     由于Future是非常常用的多線程設(shè)計模式,因此在JDK中內(nèi)置了Future模式的實現(xiàn)。這些類在java.util.concurrent包里面。其中最為重要的是FutureTask類,它實現(xiàn)了Runnable接口,作為單獨的線程運行。在其run()方法中,通過Sync內(nèi)部類調(diào)用Callable接口,并維護Callable接口的返回對象。當(dāng)使用FutureTask.get()方法時,將返回Callable接口的返回對象。其核心結(jié)構(gòu)圖如下所示:


    圖片.png

JDK內(nèi)置的Future模式功能強大,除了基本的功能外,它還可以取消Future任務(wù),或者設(shè)定future任務(wù)的超時時間。Callable接口是一個用戶自定義的實現(xiàn)。在應(yīng)用程序中,通過實現(xiàn)Callable接口的call()方法,指定FutureTask的實際工作內(nèi)容和返回對象。

Future接口提供的線程控制功能有:

1 boolean cancle(boolean mayInterruptIfRunning);  // 取消任務(wù)
2 boolean isCancelled();  // 是否已經(jīng)取消
3 boolean isDone();    // 是否已經(jīng)完成
4 V get() throws InterruptedException, ExecutionException;  //取得返回對象
5 V get(long timeout, TimeUnit unit);  //取得返回對象,可以設(shè)置超時時間

同樣,針對上述的實例,如果使用JDK自帶的實現(xiàn),則需要作一些調(diào)整。

首先,需要實現(xiàn)Callable接口,實現(xiàn)具體的業(yè)務(wù)邏輯。在本例中,依然使用RealData來實現(xiàn)這個接口:

 1 public class RealData implements Callable<String> {
 2     private String para;
 3 
 4     public RealData(String para) {
 5         this.para = para;
 6     }
 7 
 8     @Override
 9     public String call() throws Exception {
10         // 利用sleep方法來表示真是業(yè)務(wù)是非常緩慢的
11         StringBuffer sb = new StringBuffer();
12         for (int i = 0; i < 10; i++) {
13             sb.append(para);
14             try {
15                 Thread.sleep(1000);
16             } catch (InterruptedException e) {
17                 e.printStackTrace();
18             }
19         }
20         return sb.toString();
21     }
22 }

在這個改進中,RealData的構(gòu)造變動非常快,因為其主要業(yè)務(wù)邏輯被移動到call()方法內(nèi),并通過call()方法返回。

Main方法修改如下,由于使用了JDK的內(nèi)置框架,Data、FutureData等對象就不再需要了。在Main方法的實現(xiàn)中,直接通過RealData構(gòu)造FutureTask,并將其作為單獨的線程運行。在提交請求后,執(zhí)行其他業(yè)務(wù)邏輯,最后通過FutureTask.get()方法,得到RealData的執(zhí)行結(jié)果。

1 public class Main {
 2     public static void main(String[] args) {
 3         FutureTask<String> future = new FutureTask<String>(new RealData("liangyongxing"));
 4         ExecutorService executor = Executors.newFixedThreadPool(1);    // 使用線程池
 5         //執(zhí)行FutureTask,相當(dāng)于上例中的client.request("name")發(fā)送請求
 6         //在這里開啟線程進行RealData的call()執(zhí)行
 7         executor.submit(future);
 8         System.out.println("請求完畢");
 9 
10         try {
11             // 這里仍然可以做額外的數(shù)據(jù)操作,這里使用sleep代替其他業(yè)務(wù)邏輯的處理
12             Thread.sleep(2000);
13             
14             /**
15              * 相當(dāng)于上例當(dāng)中的 data.getResult(),取得call()方法的返回值
16              * 如果此時call()方法沒有執(zhí)行完畢,則依然會等待
17              */
18             System.out.println("數(shù)據(jù) = " + future.get());
19         } catch (InterruptedException | ExecutionException e) {
20             e.printStackTrace();
21         } finally {
              executor.shutdown();
          }
22     }
23 }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,881評論 18 139
  • 當(dāng)我們創(chuàng)建一個線程時,我們想獲取線程運行完成后的結(jié)果,一般使用回調(diào)的方式。例如: 運行結(jié)果: 這種方式的實現(xiàn)有三個...
    wo883721閱讀 5,724評論 2 9
  • Java-Review-Note——4.多線程 標簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,673評論 2 17
  • 1.官網(wǎng)下載最新的Git(https://git-scm.com/download/): 2.下載好以后安裝git...
    給你一顆小瓜子閱讀 42,168評論 2 11
  • 總是留不住時光的腳步,驀然回首,發(fā)現(xiàn),回憶依舊,秋葉早已飄落,翻看那如歷史畫卷般的相冊,照片早已泛黃褪色,但是那里...
    墻頭花閱讀 200評論 1 2