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

我们可以看见,在快递员送厨具的期间,我们没有闲着,可以去买食材;而且我们知道厨具到没到,甚至可以在厨具没到的时候,取消订单不要了。这也给我们节省了不少时间。

猜你喜欢

转载自blog.csdn.net/loumoxiaozi/article/details/89520542