1. Future的應用場景
在併發編程中,我們經常用到非阻塞的模型,在之前的多線程的三種實現中,不管是繼承thread類還是實現runnable接口,都無法保證獲取到之前的執行結果。通過實現Callback接口,並用Future可以來接收多線程的執行結果。
Future表示一個可能還沒有完成的異步任務的結果,針對這個結果可以添加Callback以便在任務執行成功或失敗後作出相應的操作。
舉個例子:
假如你突然想爲你的愛人做一道營養早餐飯,但是沒有廚具,也沒有食材,所以我們要先買好廚具和食材才能去做 做飯這件事,
我們傳統做法(實例1):
public class Kitchen {
}
public class Food {
}
public class BuyKitchen extends Thread {
Kitchen kitchen;
@Override
public void run() {
System.out.println("第一步:下單廚具");
System.out.println("第一步:等待廚具送貨");
try {
Thread.sleep(5000); // 模擬送貨時間
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一步:快遞送到廚具");
kitchen = new Kitchen();
}
}
public class BuyFood extends Thread{
Food food;
@Override
public void run() {
System.out.println("第二步:下單食材");
System.out.println("第二步:等待食材送貨");
try {
Thread.sleep(5000); // 模擬送貨時間
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二步:快遞送到食材");
food = new Food();
}
}
public class Cooking extends Thread{
Kitchen kitchen;
Food food;
public Cooking(Kitchen kitchen, Food food){
this.kitchen=kitchen;
this.food=food;
}
@Override
public void run() {
try {
cook(kitchen, food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 用廚具烹飪食材
private void cook(Kitchen kitchen, Food food) throws InterruptedException {
if(food!=null&&kitchen!=null) {
System.out.println("第三步:開始展現廚藝");
Thread.sleep(2000);
System.out.println("第三步:結束烹飪");
}
}
}
public class SimpleThreadCook {
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
// 第一步 網購廚具
BuyKitchen threadKitchen = new BuyKitchen();
threadKitchen.start();
threadKitchen.join(); // 保證廚具送到
// 第二步 去超市購買食材
BuyFood threadFood = new BuyFood();
threadFood.start();
threadFood.join(); // 保證廚具送到
// 第三步 用廚具烹飪食材
Cooking cooking = new Cooking(threadKitchen.kitchen, threadFood.food);
cooking.start();
cooking.join();
System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
}
}
運行結果:
第一步:下單廚具
第一步:等待廚具送貨
第一步:快遞送到廚具
第二步:下單食材
第二步:等待食材送貨
第二步:快遞送到食材
第三步:開始展現廚藝
第三步:結束烹飪
總共用時12003ms
可以看到,多線程已經失去了意義。在廚具送到期間,我們不能幹任何事。對應代碼,就是調用join方法阻塞主線程。
那我們能不能不阻塞主線程行不行???
NO!!!
從代碼來看的話,run方法不執行完,屬性kitchen 就沒有被賦值,還是null。換句話說,沒有廚具,怎麼做飯。
其實 kitchen 和 food 缺一不可,試想一下,如果我們BuyKitchen 與 BuyFood 中任何一個拋出異常,導致 kitchen 和 food 其中一個賦值失敗,那麼我們的 cook 都是 無法完成
Java現在的多線程機制,核心方法run是沒有返回值的;如果要保存run方法裏面的計算結果,必須等待run方法計算完,無論計算過程多麼耗時。
面對這種尷尬的處境,程序員就會想:我們在BuyKitchen時, 同時BuyFood,在我們 BuyKitchen 與 BuyFood 同時成功後,再去開始cook,這樣就會節省許多時間。
這種想法的核心就是Future模式,下面先應用一下Java自己實現的Future模式。
Future模式 (實例2):
public class FutureCook {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long startTime = System.currentTimeMillis();
// 第一步 網購廚具
Callable<Kitchen> buyKitchen = () -> {
System.out.println("第一步:下單廚具");
System.out.println("第一步:等待廚具送貨");
Thread.sleep(5000); // 模擬送貨時間
System.out.println("第一步:快遞送到廚具");
return new Kitchen();
};
FutureTask<Kitchen> taskKitchen = new FutureTask<Kitchen>(buyKitchen);
Thread threadKitchen = new Thread(taskKitchen);
threadKitchen.start();
// 第二步 去超市購買食材
Callable<Food> buyFood = () -> {
System.out.println("第二步:下單食材");
System.out.println("第二步:等待食材送貨");
Thread.sleep(5000); // 模擬送貨時間
System.out.println("第二步:快遞送到食材");
return new Food();
};
FutureTask<Food> taskFood = new FutureTask<Food>(buyFood);
Thread threadFood = new Thread(taskFood);
threadFood.start();
// 第三步 用廚具烹飪食材
if (!taskKitchen.isDone()||!taskFood.isDone()) { // 聯繫快遞員,詢問是否到貨
System.out.println("第三步:廚具或食材還沒到,心情好就等着(心情不好就調用cancel方法取消訂單)");
}
Cooking cooking = new Cooking(taskKitchen.get(), taskFood.get());
cooking.start();
cooking.join();
System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
}
}
運行結果:
第一步:下單廚具
第一步:等待廚具送貨
第三步:廚具還沒到,心情好就等着(心情不好就調用cancel方法取消訂單)
第二步:下單食材
第二步:等待食材送貨
第一步:快遞送到廚具
第二步:快遞送到食材
第三步:開始展現廚藝
第三步:結束烹飪
總共用時7043ms
我們可以看見,在快遞員送廚具的期間,我們沒有閒着,可以去買食材;而且我們知道廚具到沒到,甚至可以在廚具沒到的時候,取消訂單不要了。這也給我們節省了不少時間。