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