Java多線程編程中届囚,常用的多線程設(shè)計(jì)模式包括:Future模式免胃、Master-Worker模式痹栖、Guarded Suspeionsion模式蛙奖、不變模式和生產(chǎn)者-消費(fèi)者模式等潘酗。這篇文章主要講述Future模式杆兵,關(guān)于其他多線程設(shè)計(jì)模式的地址如下:
關(guān)于Master-Worker模式的詳解: 并行設(shè)計(jì)模式(二)-- Master-Worker模式
關(guān)于Guarded Suspeionsion模式的詳解: 并行設(shè)計(jì)模式(三)-- Guarded Suspeionsion模式
關(guān)于不變模式的詳解: 并行設(shè)計(jì)模式(四)-- 不變模式
關(guān)于生產(chǎn)者-消費(fèi)者模式的詳解:并行設(shè)計(jì)模式(五)-- 生產(chǎn)者-消費(fèi)者模式
- Future模式
Future模式的核心在于:去除了主函數(shù)的等待時(shí)間雁仲,并使得原本需要等待的時(shí)間段可以用于處理其他業(yè)務(wù)邏輯。
Future模式有點(diǎn)類似于商品訂單琐脏。在網(wǎng)上購物時(shí)攒砖,提交訂單后缸兔,在收貨的這段時(shí)間里無需一直在家里等候,可以先干別的事情吹艇。類推到程序設(shè)計(jì)中時(shí)惰蜜,當(dāng)提交請求時(shí),期望得到答復(fù)時(shí)受神,如果這個(gè)答復(fù)可能很慢抛猖。傳統(tǒng)的是一直持續(xù)等待直到這個(gè)答復(fù)收到之后再去做別的事情,但如果利用Future模式鼻听,其調(diào)用方式改為異步财著,而原先等待返回的時(shí)間段,在主調(diào)用函數(shù)中撑碴,則可以用于處理其他事務(wù)撑教。
例如如下的請求調(diào)用過程時(shí)序圖。當(dāng)call請求發(fā)出時(shí)醉拓,需要很長的時(shí)間才能返回伟姐。左邊的圖需要一直等待,等返回?cái)?shù)據(jù)后才能繼續(xù)其他操作亿卤;而右邊的Future模式的圖中客戶端則無需等到可以做其他的事情愤兵。服務(wù)器段接收到請求后立即返回結(jié)果給客戶端,這個(gè)結(jié)果并不是真實(shí)的結(jié)果(是虛擬的結(jié)果)排吴,也就是先獲得一個(gè)假數(shù)據(jù)恐似,然后執(zhí)行其他操作。
Future模式的主要參與者如下表所示:
-
Future模式的代碼實(shí)現(xiàn)
圖片.png
<1. Main函數(shù)的實(shí)現(xiàn)
Main函數(shù)主要負(fù)責(zé)調(diào)用Client發(fā)起請求傍念,并使用返回的數(shù)據(jù):
public class Main {
public static void main(String[] args) {
Client client = new Client();
// 這里會立即返回矫夷,因?yàn)楂@取的是FutureData,而非RealData
Data data = client.request("name");
System.out.println("請求完畢");
try {
// 這里可以用一個(gè)sleep代替對其他業(yè)務(wù)邏輯的處理
// 在處理這些業(yè)務(wù)邏輯過程中憋槐,RealData也正在創(chuàng)建双藕,從而充分了利用等待時(shí)間
Thread.sleep(2000);
// 使用真實(shí)數(shù)據(jù)
System.out.println("數(shù)據(jù)=" + data.getResult());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
<2. Client的實(shí)現(xiàn)
Client主要實(shí)現(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)建很慢,所以放在單獨(dú)的線程中運(yùn)行
RealData realData = new RealData(string);
futureData.setRealData(realData);
}
}).start();
return futureData; // 先直接返回FutureData
}
}
<3. Data的實(shí)現(xiàn)
Data是一個(gè)接口近范,提供了getResult()方法嘶摊。無論futureData或者RealData都實(shí)現(xiàn)了這個(gè)接口
public interface Data {
String getResult() throws InterruptedException;
}
<4. FutureData的實(shí)現(xiàn)
FutureData實(shí)現(xiàn)了一個(gè)快速返回的RealData包裝。它只是一個(gè)包裝评矩,或者說是一個(gè)RealData的虛擬實(shí)現(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)準(zhǔn)備好
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的實(shí)現(xiàn)
RealData是最終需要使用的數(shù)據(jù)模型,它的構(gòu)造很慢畦粮。在這里散址,使用sleep()函數(shù)模擬這個(gè)過程
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模式實(shí)現(xiàn)
由于Future是非常常用的多線程設(shè)計(jì)模式,因此在JDK中內(nèi)置了Future模式的實(shí)現(xiàn)宣赔。這些類在java.util.concurrent包里面爪飘。其中最為重要的是FutureTask類,它實(shí)現(xiàn)了Runnable接口拉背,作為單獨(dú)的線程運(yùn)行师崎。在其run()方法中,通過Sync內(nèi)部類調(diào)用Callable接口椅棺,并維護(hù)Callable接口的返回對象犁罩。當(dāng)使用FutureTask.get()方法時(shí),將返回Callable接口的返回對象两疚。其核心結(jié)構(gòu)圖如下所示:
圖片.png
JDK內(nèi)置的Future模式功能強(qiáng)大床估,除了基本的功能外,它還可以取消Future任務(wù)诱渤,或者設(shè)定future任務(wù)的超時(shí)時(shí)間丐巫。Callable接口是一個(gè)用戶自定義的實(shí)現(xiàn)。在應(yīng)用程序中勺美,通過實(shí)現(xiàn)Callable接口的call()方法递胧,指定FutureTask的實(shí)際工作內(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è)置超時(shí)時(shí)間
同樣缎脾,針對上述的實(shí)例,如果使用JDK自帶的實(shí)現(xiàn)占卧,則需要作一些調(diào)整遗菠。
首先,需要實(shí)現(xiàn)Callable接口华蜒,實(shí)現(xiàn)具體的業(yè)務(wù)邏輯辙纬。在本例中,依然使用RealData來實(shí)現(xiàn)這個(gè)接口:
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 }
在這個(gè)改進(jìn)中叭喜,RealData的構(gòu)造變動非澈丶穑快,因?yàn)槠渲饕獦I(yè)務(wù)邏輯被移動到call()方法內(nèi)域滥,并通過call()方法返回纵柿。
Main方法修改如下,由于使用了JDK的內(nèi)置框架启绰,Data昂儒、FutureData等對象就不再需要了。在Main方法的實(shí)現(xiàn)中委可,直接通過RealData構(gòu)造FutureTask渊跋,并將其作為單獨(dú)的線程運(yùn)行。在提交請求后着倾,執(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 //在這里開啟線程進(jìn)行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 * 如果此時(shí)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 }