異步編程還得看JDK8
話說(shuō),不看不知道,都說(shuō)JDK7當(dāng)時(shí)升級(jí)JDK8的時(shí)候,升級(jí)了非常多的內(nèi)容,不得不說(shuō),這升級(jí)真的大。
什么是異步編程
在很多時(shí)候,我們?cè)谶M(jìn)程中使用單一線程從頭到尾地執(zhí)行程序,比如程序向另外一臺(tái)服務(wù)器發(fā)出請(qǐng)求,由于網(wǎng)絡(luò)等外部原因,此種通信任務(wù)往往會(huì)耗費(fèi)大量時(shí)間,進(jìn)程如果在此期間僅僅只能等待網(wǎng)絡(luò)或網(wǎng)絡(luò)上其他機(jī)器的響應(yīng),將嚴(yán)重地降低了性能。
如果程序調(diào)用某個(gè)方法,等待其執(zhí)行全部處理后才能繼續(xù)執(zhí)行,我們稱其為同步的。相反,在處理完成之前就返回調(diào)用方法則是異步的。
我們?cè)诰幊陶Z(yǔ)言的流程中添加了異步控制的部分,這部分的編程可以稱之為異步編程。
JDK中的異步編程
Future
Future模式在 JDK5 的時(shí)候就有, Future模式,只是發(fā)起了耗時(shí)操作,函數(shù)立馬就返回了,真正執(zhí)行具體操作由另外一個(gè)工作線程去完成,并不會(huì)阻塞客戶端線程。所以在工作線程執(zhí)行耗時(shí)操作的時(shí)候客戶端無(wú)需等待,可以繼續(xù)做其他事情,等到需要的時(shí)候再向工作線程獲取結(jié)果。
舉個(gè)最簡(jiǎn)單的例子,我們燒水的時(shí)候么,不用一直在爐子旁邊看著,在燒水的過(guò)程中,我們需要做一些其他的事情,比如去寫一會(huì)代碼,但是在你去寫代碼之前,會(huì)給你一個(gè)假的結(jié)果,比如,我已經(jīng)燒開了,但是,在你去寫代碼的時(shí)候,他就開始瘋狂加火,等到水燒開為止,等到你口渴想倒水的時(shí)候,發(fā)現(xiàn)水是已經(jīng)燒開的,也就是說(shuō),當(dāng)你在寫代碼之前的時(shí)候收到的是個(gè)假的結(jié)果。
實(shí)際上,F(xiàn)uture 模式無(wú)法立即給出你想要的結(jié)果,但它會(huì)給你一個(gè)契約,之后你可以隨時(shí)通過(guò)這個(gè)契約來(lái)獲取你想要的結(jié)果。
異步模式主要是和同步模式進(jìn)行對(duì)比的,我們畫個(gè)圖來(lái)看看。
黃色區(qū)域的位置的Y軸長(zhǎng)度則表示的是你需要等待的所有時(shí)間,在這個(gè)時(shí)間內(nèi),你沒(méi)有辦法做任何的事情,只能在這里等著,但是異步的話,就完全不用這個(gè)樣子了。
在JDK中Future模式有一套完整的實(shí)現(xiàn)。
我們來(lái)寫案例代碼實(shí)驗(yàn)一下:
沒(méi)有是用 Future 的代碼。
NormalThreadTest
public class NormalThreadTest {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
// 開啟購(gòu)買廚具線程
ShoppingThread shopping = new ShoppingThread();
shopping.start();
shopping.join(); // 保障廚具購(gòu)買并送貨
// 獲取到購(gòu)買廚具
KitchenWare kc = shopping.kc;
// 買食材
FoodMaterial fm = new FoodMaterial();
Thread.sleep(2000);
System.out.println("第二步: 食材已經(jīng)到位");
// 烹飪美食
cooking(kc, fm);
System.out.println("第三步: 美食烹飪完成");
long end = System.currentTimeMillis();
System.out.println("烹飪美食時(shí)間為:" + (end - start));
}
/**
* 定義網(wǎng)上購(gòu)物廚具線程
* @author Administrator
*
*/
static class ShoppingThread extends Thread {
// 廚具對(duì)象引用
private KitchenWare kc;
@Override
public void run() {
System.out.println("第一步: 網(wǎng)上下單");
System.out.println("第一步: 等待廚具");
try {
Thread.sleep(5000); // 等待廚具時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一步: 快遞送貨");
// 生產(chǎn)廚具
kc = new KitchenWare();
}
}
/**
* 廚具類
* @author Administrator
*
*/
static class KitchenWare {
}
/**
* 食材類
* @author Administrator
*
*/
static class FoodMaterial {
}
/**
* 定義烹飪食物的方法
* @param kc
* @param fm
*/
static void cooking(KitchenWare kc, FoodMaterial fm) {
}
}
運(yùn)行結(jié)果:
第一步: 網(wǎng)上下單
第一步: 等待廚具
第一步: 快遞送貨
第二步: 食材已經(jīng)到位
第三步: 美食烹飪完成
烹飪美食時(shí)間為:7043
已經(jīng)使用Future的代碼。
FutureThreadTest
public class FutureThreadTest {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
Callable<KitchenWare> callable = new Callable<KitchenWare>() {
public KitchenWare call() throws Exception {
System.out.println("第一步: 網(wǎng)上下單");
System.out.println("第一步: 等待廚具");
try {
Thread.sleep(5000); // 等待廚具時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一步: 快遞送貨");
return new KitchenWare();
}
};
// 包裝為異步執(zhí)行的對(duì)象
FutureTask<KitchenWare> task = new FutureTask<>(callable);
new Thread(task).start();
// 買食材
FoodMaterial fm = new FoodMaterial();
Thread.sleep(2000);
System.out.println("第二步: 食材已經(jīng)到位");
if (!task.isDone()) {
System.out.println("廚具還沒(méi)有到.....");
}
// 通過(guò)阻塞形式獲取到異步塊執(zhí)行的結(jié)果
KitchenWare kc = task.get(); // 阻塞
// 烹飪美食
cooking(kc, fm);
System.out.println("第三步: 美食烹飪完成");
long end = System.currentTimeMillis();
System.out.println("烹飪美食時(shí)間為:" + (end - start));
}
/**
* 廚具類
* @author Administrator
*
*/
static class KitchenWare {
}
/**
* 食材類
* @author Administrator
*
*/
static class FoodMaterial {
}
/**
* 定義烹飪食物的方法
* @param kc
* @param fm
*/
static void cooking(KitchenWare kc, FoodMaterial fm) {
}
}
執(zhí)行結(jié)果:
第一步: 網(wǎng)上下單
第一步: 等待廚具
第二步: 食材已經(jīng)到位
廚具還沒(méi)有到.....
第一步: 快遞送貨
第三步: 美食烹飪完成
烹飪美食時(shí)間為:5027
這個(gè)是JDK5中就有的 Future 來(lái)實(shí)現(xiàn) 異步編程的,那么接下來(lái)我們看1.8的異步編程。
CompletableFuture
Future 雖然可以實(shí)現(xiàn)獲取異步執(zhí)行結(jié)果的需求,但是它沒(méi)有提供通知的機(jī)制,我們無(wú)法得知Future什么時(shí)候完成,我們通過(guò)上面的代碼也完全能看出來(lái)。
為什么在JDK5之后,又推出新的異步編程,因?yàn)槭褂?Future 要么使用阻塞,在 future.get() 的地方等待 Future 返回的結(jié)果,這時(shí)又變成同步操作。要么使用 isDone() 輪詢地判斷 Future 是否完成,這樣會(huì)耗費(fèi)CPU的資源。所以阿粉猜測(cè)所以在JDK8又推出了 CompletableFuture。
之前 Future 需要等待 isDone 為 true 才能知道任務(wù)跑完了?;蛘呔褪怯?get 方法調(diào)用的時(shí)候會(huì)出現(xiàn)阻塞。而使用 CompletableFuture 的使用就可以用 then , when 等等操作來(lái)防止以上的阻塞和輪詢 isDone 的現(xiàn)象出現(xiàn)。
CompletableFuture 有四個(gè)方法來(lái)創(chuàng)建CompletableFuture對(duì)象。
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
Asynsc表示異步,而supplyAsync與runAsync不同在與前者異步返回一個(gè)結(jié)果,后者是void.第二個(gè)函數(shù)第二個(gè)參數(shù)表示是用我們自己創(chuàng)建的線程池,否則采用默認(rèn)的ForkJoinPool.commonPool()作為它的線程池.其中Supplier是一個(gè)函數(shù)式接口,代表是一個(gè)生成者的意思,傳入0個(gè)參數(shù),返回一個(gè)結(jié)果。
我們寫一個(gè)最簡(jiǎn)單的測(cè)試代碼:
public static void test2() throws Exception {
//supplyAsync內(nèi)部使用ForkJoinPool線程池執(zhí)行任務(wù)
CompletableFuture<String> completableFuture=CompletableFuture.supplyAsync(()->{
//模擬執(zhí)行耗時(shí)任務(wù)
System.out.println("task doing...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//返回結(jié)果
return "100";
}).whenComplete((v,r)->{
System.out.println("計(jì)算結(jié)果是: "+v);
});
//CompletableFuture里使用的線程池里的線程默認(rèn)是daemon的。main線程結(jié)束后,整個(gè)程序也
//結(jié)束了,這里將main線程join后任務(wù)里的代碼才可以執(zhí)行完
Thread.currentThread().join();
}
而使用 CompletableFuture 能有效的避開使用 Futrue 出現(xiàn)的缺點(diǎn)。
看來(lái),JDK 每一次的更新?lián)Q代,不光是加了一些新的內(nèi)容,而且像開發(fā)一樣,每次迭代的時(shí)候,同時(shí)也會(huì)更新之前的一些不完美的內(nèi)容,不是么?