1.1、Future模式是什么
先簡單舉個(gè)例子介紹堪伍,當(dāng)我們平時(shí)寫一個(gè)函數(shù),函數(shù)里的語句一行行同步執(zhí)行觅闽,如果某一行執(zhí)行很慢帝雇,程序就必須等待,直到執(zhí)行結(jié)束才返回結(jié)果蛉拙;但有時(shí)我們可能并不急著需要其中某行的執(zhí)行結(jié)果尸闸,想讓被調(diào)用者立即返回。比如小明在某網(wǎng)站上成功創(chuàng)建了一個(gè)賬號孕锄,創(chuàng)建完賬號后會(huì)有郵件通知吮廉,如果在郵件通知時(shí)因某種原因耗時(shí)很久(此時(shí)賬號已成功創(chuàng)建),使用傳統(tǒng)同步執(zhí)行的方式那就要等完這個(gè)時(shí)間才會(huì)有創(chuàng)建成功的結(jié)果返回到前端畸肆,但此時(shí)賬號創(chuàng)建成功后我們并不需要立即關(guān)心郵件發(fā)送成功了沒宦芦,此時(shí)就可以使用Future模式,讓安在后臺慢慢處理這個(gè)請求轴脐,對于調(diào)用者來說调卑,則可以先處理一些其他任務(wù),在真正需要數(shù)據(jù)的場合(比如某時(shí)想要知道郵件發(fā)送是否成功)再去嘗試獲取需要的數(shù)據(jù)大咱。
使用Future模式,獲取數(shù)據(jù)的時(shí)候可能無法立即得到需要的數(shù)據(jù)恬涧。而是先拿到一個(gè)包裝,可以在需要的時(shí)候再去get獲取需要的數(shù)據(jù)。
1.2碴巾、Future模式與傳統(tǒng)模式的區(qū)別
先看看請求返回的時(shí)序圖溯捆,明顯傳統(tǒng)的模式是串行同步執(zhí)行的,在遇到耗時(shí)操作的時(shí)候只能等待。反觀Future模式餐抢,發(fā)起一個(gè)耗時(shí)操作后现使,函數(shù)會(huì)立刻返回,并不會(huì)阻塞客戶端線程旷痕。所以在執(zhí)行實(shí)際耗時(shí)操作時(shí)候客戶端無需等待碳锈,可以做其他事情,直到需要的時(shí)候再向工作線程獲取結(jié)果。
2.1欺抗、動(dòng)手實(shí)現(xiàn)簡易Future模式
下面的DataFuture類只是一個(gè)包裝類售碳,創(chuàng)建它時(shí)無需阻塞等待。在工作線程準(zhǔn)備好數(shù)據(jù)后使用setRealData方法將數(shù)據(jù)傳入绞呈∶橙耍客戶端只要在真正需要數(shù)據(jù)時(shí)調(diào)用getRealData方法即可,如果此時(shí)數(shù)據(jù)已準(zhǔn)備好則立即返回佃声,否則getRealData方法就會(huì)等待艺智,直到獲取數(shù)據(jù)完成。
public class DataFuture<T> {
private T realData;
private boolean isOK = false;
public synchronized T getRealData() {
while (!isOK) {
try {
// 數(shù)據(jù)未準(zhǔn)備好則等待
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
return realData;
}
public synchronized void setRealData(T data) {
isOK = true;
realData = data;
notifyAll();
}
}
下面實(shí)現(xiàn)一服務(wù)端圾亏,客戶端向服務(wù)端請求數(shù)據(jù)時(shí)十拣,服務(wù)端并不會(huì)立刻去加載真正數(shù)據(jù),只是創(chuàng)建一個(gè)DataFuture志鹃,創(chuàng)建子線程去加載真正數(shù)據(jù)夭问,服務(wù)端直接返回DataFuture即可。
public class Server {
public DataFuture<String> getData() {
final DataFuture<String> data = new DataFuture<>();
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
data.setRealData("最終數(shù)據(jù)");
}
});
return data;
}
}
最終客戶端調(diào)用 代碼如下:
long start = System.currentTimeMillis();
Server server = new Server();
DataFuture<String> dataFuture = server.getData();
try {
// 先執(zhí)行其他操作
Thread.sleep(5000);
// 模擬耗時(shí)...
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("結(jié)果數(shù)據(jù):" + dataFuture.getRealData());
System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));
結(jié)果:
結(jié)果數(shù)據(jù):最終數(shù)據(jù)
耗時(shí): 5021
執(zhí)行最終數(shù)據(jù)耗時(shí)都在5秒左右曹铃,如果串行執(zhí)行的話就是10秒左右。
2.2陕见、JDK中的Future與FutureTask
先來看看Future接口源碼:
public interface Future<V> {
/**
* 用來取消任務(wù),取消成功則返回true淳玩,取消失敗則返回false。
* mayInterruptIfRunning參數(shù)表示是否允許取消正在執(zhí)行卻沒有執(zhí)行完畢的任務(wù)蜕着,設(shè)為true谋竖,則表示可以取消正在執(zhí)行過程中的任務(wù)承匣。
* 如果任務(wù)已完成蓖乘,則無論mayInterruptIfRunning為true還是false韧骗,此方法都返回false嘉抒,即如果取消已經(jīng)完成的任務(wù)會(huì)返回false;
* 如果任務(wù)正在執(zhí)行袍暴,若mayInterruptIfRunning設(shè)置為true隶症,則返回true岗宣,若mayInterruptIfRunning設(shè)置為false蚂会,則返回false耗式;
* 如果任務(wù)還沒有執(zhí)行,則無論mayInterruptIfRunning為true還是false彪见,肯定返回true娱挨。
*/
boolean cancel(boolean mayInterruptIfRunning);
/**
* 表示任務(wù)是否被取消成功,如果在任務(wù)正常完成前被取消成功让蕾,則返回true
*/
boolean isCancelled();
/**
* 表示任務(wù)是否已經(jīng)完成,若任務(wù)完成笋婿,則返回true
*/
boolean isDone();
/**
* 獲取執(zhí)行結(jié)果顿颅,如果最終結(jié)果還沒得出該方法會(huì)產(chǎn)生阻塞,直到任務(wù)執(zhí)行完畢返回結(jié)果
*/
V get() throws InterruptedException, ExecutionException;
/**
* 獲取執(zhí)行結(jié)果粱腻,如果在指定時(shí)間內(nèi),還沒獲取到結(jié)果捞慌,則拋出TimeoutException
*/
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
從上面源碼可看出Future就是對于Runnable或Callable任務(wù)的執(zhí)行進(jìn)行查詢柬批、中斷任務(wù)、獲取結(jié)果氮帐。下面就以一個(gè)計(jì)算1到1億的和為例子,看使用傳統(tǒng)方式和使用Future耗時(shí)差多少皮服。先看傳統(tǒng)方式代碼:
public class FutureTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
List<Integer> retList = new ArrayList<>();
// 計(jì)算1000次1至1億的和
for (int i = 0; i < 1000; i++) {
retList.add(Calc.cal(100000000));
}
System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));
for (int i = 0; i < 1000; i++) {
try {
Integer result = retList.get(i);
System.out.println("第" + i + "個(gè)結(jié)果: " + result);
} catch (Exception e) {
}
}
System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));
}
public static class Calc implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return cal(10000);
}
public static int cal (int num) {
int sum = 0;
for (int i = 0; i < num; i++) {
sum += i;
}
return sum;
}
}
}
執(zhí)行結(jié)果(耗時(shí)40+秒):
耗時(shí): 43659
第0個(gè)結(jié)果: 887459712
第1個(gè)結(jié)果: 887459712
第2個(gè)結(jié)果: 887459712
...
第999個(gè)結(jié)果: 887459712
耗時(shí): 43688
再來看看使用Future模式下程序:
public class FutureTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<Integer>> futureList = new ArrayList<>();
// 計(jì)算1000次1至1億的和
for (int i = 0; i < 1000; i++) {
// 調(diào)度執(zhí)行
futureList.add(executorService.submit(new Calc()));
}
System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));
for (int i = 0; i < 1000; i++) {
try {
Integer result = futureList.get(i).get();
System.out.println("第" + i + "個(gè)結(jié)果: " + result);
} catch (InterruptedException | ExecutionException e) {
}
}
System.out.println("耗時(shí): " + (System.currentTimeMillis() - start));
}
public static class Calc implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return cal(100000000);
}
public static int cal (int num) {
int sum = 0;
for (int i = 0; i < num; i++) {
sum += i;
}
return sum;
}
}
}
執(zhí)行結(jié)果(耗時(shí)12+秒):
耗時(shí): 12058
第0個(gè)結(jié)果: 887459712
第1個(gè)結(jié)果: 887459712
...
第999個(gè)結(jié)果: 887459712
耗時(shí): 12405
可以看到龄广,計(jì)算1000次1至1億的和,使用Future模式并發(fā)執(zhí)行最終的耗時(shí)比使用傳統(tǒng)的方式快了30秒左右舟铜,使用Future模式的效率大大提高奠衔。
2.3塘娶、FutureTask
說完Future,F(xiàn)uture因?yàn)槭墙涌诓荒苤苯佑脕韯?chuàng)建對象刁岸,就有了下面的FutureTask。
先看看FutureTask的實(shí)現(xiàn):
public class FutureTask<V> implements RunnableFuture<V>
可以看到FutureTask類實(shí)現(xiàn)了RunnableFuture接口虹曙,接著看RunnableFuture接口源碼:
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
可以看到RunnableFuture接口繼承了Runnable接口和Future接口,也就是說其實(shí)FutureTask既可以作為Runnable被線程執(zhí)行矾踱,也可以作為Future得到Callable的返回值疏哗。
看下面FutureTask的兩個(gè)構(gòu)造方法,可以看出就是為這兩個(gè)操作準(zhǔn)備的返奉。
public FutureTask(Callable<V> var1) {
if (var1 == null) {
throw new NullPointerException();
} else {
this.callable = var1;
this.state = 0;
}
}
public FutureTask(Runnable var1, V var2) {
this.callable = Executors.callable(var1, var2);
this.state = 0;
}
FutureTask使用實(shí)例:
public class FutureTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Calc task = new Calc();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
executor.shutdown();
}
public static class Calc implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return cal(100000000);
}
public static int cal (int num) {
int sum = 0;
for (int i = 0; i < num; i++) {
sum += i;
}
return sum;
}
}
}
2.4芽偏、Future不足之處
上面例子可以看到使用Future模式比傳統(tǒng)模式效率明顯提高了,使用Future一定程度上可以讓一個(gè)線程池內(nèi)的任務(wù)異步執(zhí)行污尉;但同時(shí)也有個(gè)明顯的缺點(diǎn):就是回調(diào)無法放到與任務(wù)不同的線程中執(zhí)行,傳統(tǒng)回調(diào)最大的問題就是不能將控制流分離到不同的事件處理器中等太。比如主線程要等各個(gè)異步執(zhí)行線程返回的結(jié)果來做下一步操作蛮放,就必須阻塞在future.get()方法等待結(jié)果返回,這時(shí)其實(shí)又是同步了,如果遇到某個(gè)線程執(zhí)行時(shí)間太長時(shí)压真,那情況就更糟了蘑险。
到Java8時(shí)引入了一個(gè)新的實(shí)現(xiàn)類CompletableFuture,彌補(bǔ)了上面的缺點(diǎn)佃迄,在下篇會(huì)講解CompletableFuture的使用。