什么是Future模式
Future模式是多線程開發(fā)中非常常見的一種設(shè)計(jì)模式外邓。它的核心思想是異步調(diào)用。當(dāng)我們需要調(diào)用一個(gè)函數(shù)方法時(shí)。如果這個(gè)函數(shù)執(zhí)行很慢,那么我們就要進(jìn)行等待。但有時(shí)候,我們可能并不急著要結(jié)果。因此,我們可以讓被調(diào)用者立即返回,讓他在后臺(tái)慢慢處理這個(gè)請(qǐng)求培他。對(duì)于調(diào)用者來說,則可以先處理一些其他任務(wù),在真正需要數(shù)據(jù)的場合再去嘗試獲取需要的數(shù)據(jù)。
用生活中的例子來打個(gè)比喻,就像叫外賣遗座。比如在午休之前我們可以提前叫外賣,只需要點(diǎn)好食物,下個(gè)單舀凛。然后我們可以繼續(xù)工作。到了中午下班的時(shí)候外賣也就到了,然后就可以吃個(gè)午餐,再美滋滋的睡個(gè)午覺途蒋。而如果你在下班的時(shí)候才叫外賣,那就只能坐在那里干等著外賣小哥,最后拿到外賣吃完午飯,午休時(shí)間也差不多結(jié)束了猛遍。
使用Future模式,獲取數(shù)據(jù)的時(shí)候無法立即得到需要的數(shù)據(jù)。而是先拿到一個(gè)契約,你可以再將來需要的時(shí)候再用這個(gè)契約去獲取需要的數(shù)據(jù),這個(gè)契約就好比叫外賣的例子里的外賣訂單号坡。
用普通方式和Future模式的差別
我們可以看一下使用普通模式和用Future模式的時(shí)序圖懊烤。可以看出來普通模式是串行的,在遇到耗時(shí)操作的時(shí)候只能等待宽堆。而Future模式,只是發(fā)起了耗時(shí)操作,函數(shù)立馬就返回了,并不會(huì)阻塞客戶端線程腌紧。所以在工作線程執(zhí)行耗時(shí)操作的時(shí)候客戶端無需等待,可以繼續(xù)做其他事情,等到需要的時(shí)候再向工作線程獲取結(jié)果:
Future模式的簡單實(shí)現(xiàn)
首先是FutureData,它是只是一個(gè)包裝類,創(chuàng)建它不需要耗時(shí)。在工作線程準(zhǔn)備好數(shù)據(jù)之后可以使用setData方法將數(shù)據(jù)傳入畜隶。而客戶端線程只需要在需要的時(shí)候調(diào)用getData方法即可,如果這個(gè)時(shí)候數(shù)據(jù)還沒有準(zhǔn)備好,那么getData方法就會(huì)等待,如果已經(jīng)準(zhǔn)備好了就好直接返回壁肋。
public class FutureData<T> {
private boolean mIsReady = false;
private T mData;
public synchronized void setData(T data) {
mIsReady = true;
mData = data;
notifyAll();
}
public synchronized T getData() {
while (!mIsReady) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return mData;
}
}
接著是服務(wù)端,客戶端在向服務(wù)端請(qǐng)求數(shù)據(jù)的時(shí)候服務(wù)端不會(huì)實(shí)際去加載數(shù)據(jù),它只是創(chuàng)建一個(gè)FutureData,然后創(chuàng)建子線程去加載,而它只需要直接返回FutureData就可以了。
public class Server {
public FutureData<String> getString() {
final FutureData<String> data = new FutureData<>();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
data.setData("world");
}
}).start();
return data;
}
}
客戶端代碼如下,整個(gè)程序只需要運(yùn)行2秒多,但如果不使用Future模式的話就需要三秒了籽慢。
Server server = new Server();
FutureData<String> futureData = server.getString();
//先執(zhí)行其他操作
String hello = "hello";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(hello + " " + futureData.getData());
JDK中的Future模式
還記得我之前的一篇文章《Java多線程 - 線程池》中寫的ExecutorService.execute()和ExecutorService.submit()的區(qū)別嗎(如果沒有看過的讀者可以去看一下)浸遗?
execute方法其實(shí)是在Executor中定義的,而ExecutorService繼承了Executor。它只是簡單的提交了一個(gè)Runnable給線程池中的線程去調(diào)用:
public interface Executor {
void execute(Runnable command);
}
public interface ExecutorService extends Executor {
...
}
而submit方法是ExecutorService中定義的,它們都會(huì)返回一個(gè)Future對(duì)象箱亿。實(shí)際上submit方法就是使用的Future模式:
public interface ExecutorService extends Executor {
...
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
...
}
Future<?> submit(Runnable task) :
它的返回值實(shí)際上是Future<Void>,子線程是不會(huì)返回?cái)?shù)據(jù)的跛锌。
<T> Future<T> submit(Runnable task, T result) :
這個(gè)方法是不是很蛋疼,返回的結(jié)果在調(diào)用的時(shí)候已經(jīng)給出了。如果我一開始就知道結(jié)果那我為什么又要發(fā)起子線程呢届惋?
其實(shí)不然,這個(gè)result可以是一個(gè)代理,它不是實(shí)際的結(jié)果,它只是存儲(chǔ)了結(jié)果髓帽。我這里給出一個(gè)例子大家體會(huì)一下吧:
final String[] result = new String[1];
Runnable r = new Runnable() {
public void run() {
result[0] = "hello world";
}
};
Future<String[]> future = Executors.newSingleThreadExecutor().submit(r, result);
try {
System.out.println("result[0]: " + future.get()[0]);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
<T> Future<T> submit(Callable<T> task) :
這個(gè)方法就比較好理解了, Callable.call()方法在子線程中被調(diào)用,同時(shí)它有返回值,只有將加載的數(shù)據(jù)直接return出來就好:
Future<String> future = Executors.newSingleThreadExecutor()
.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello World";
}
});
try {
System.out.print(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
一個(gè)實(shí)際的例子
比如我們?cè)谟?jì)算兩個(gè)List<Integer>中的數(shù)的總和的時(shí)候就可以用Future模式提高效率:
public int getTotal(final List<Integer> a, final List<Integer> b) throws ExecutionException, InterruptedException {
Future<Integer> future = Executors.newCachedThreadPool().submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int r = 0;
for (int num : a) {
r += num;
}
return r;
}
});
int r = 0;
for (int num : b) {
r += num;
}
return r + future.get();
}