java多線程之Future模式 應用

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

我們可以看見,在快遞員送廚具的期間,我們沒有閒着,可以去買食材;而且我們知道廚具到沒到,甚至可以在廚具沒到的時候,取消訂單不要了。這也給我們節省了不少時間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章