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)者-消費者模式
- 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í)行其他操作。
Future模式的主要參與者如下表所示:
-
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;
}
}
-
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 }