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