java 多线程 合并多个查询结果

场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。

实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。

但是,子线程执行的结果是要返回厨具的,而run方法是没有返回值的。所以,这才是难点,需要好好考虑一下。

模拟代码:

 1 package test;
 2 
 3 import java.util.concurrent.Callable;
 4 import java.util.concurrent.ExecutionException;
 5 import java.util.concurrent.FutureTask;
 6 
 7 public class FutureCook {
 8 
 9     public static void main(String[] args) throws InterruptedException, ExecutionException {
10         long startTime = System.currentTimeMillis();
11         // 第一步 网购厨具
12         Callable<Chuju> onlineShopping = new Callable<Chuju>() {
13 
14             @Override
15             public Chuju call() throws Exception {
16                 System.out.println("第一步:下单");
17                 System.out.println("第一步:等待送货");
18                 Thread.sleep(5000);  // 模拟送货时间
19                 System.out.println("第一步:快递送到");
20                 return new Chuju();
21             }
22             
23         };
24         FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
25         new Thread(task).start();
26         // 第二步 去超市购买食材
27         Thread.sleep(2000);  // 模拟购买食材时间
28         Shicai shicai = new Shicai();
29         System.out.println("第二步:食材到位");
30         // 第三步 用厨具烹饪食材
31         if (!task.isDone()) {  // 联系快递员,询问是否到货
32             System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");
33         }
34         Chuju chuju = task.get();
35         System.out.println("第三步:厨具到位,开始展现厨艺");
36         cook(chuju, shicai);
37         
38         System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
39     }
40     
41     //  用厨具烹饪食材
42     static void cook(Chuju chuju, Shicai shicai) {}
43     
44     // 厨具类
45     static class Chuju {}
46     
47     // 食材类
48     static class Shicai {}
49 
50 }

结果

1 第一步:下单
2 第一步:等待送货
3 第二步:食材到位
4 第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)
5 第一步:快递送到
6 第三步:厨具到位,开始展现厨艺
7 总共用时5005ms

下面具体分析一下这段代码:

1)把耗时的网购厨具逻辑,封装到了一个Callable的call方法里面。

java 多线程  合并多个查询结果
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
java 多线程  合并多个查询结果

 Callable接口可以看作是Runnable接口的补充,call方法带有返回值,并且可以抛出异常。

2)把Callable实例当作参数,生成一个FutureTask的对象,然后把这个对象当作一个Runnable,作为参数另起线程。

public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
java 多线程  合并多个查询结果
public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
java 多线程  合并多个查询结果

这个继承体系中的核心接口是Future。Future的核心思想是:一个方法f,计算过程可能非常耗时,等待f返回,显然不明智。可以在调用f的时候,立马返回一个Future,可以通过Future这个数据结构去控制方法f的计算过程。

这里的控制包括:

get方法:获取计算结果(如果还没计算完,也是必须等待的)

cancel方法:还没计算完,可以取消计算过程

isDone方法:判断是否计算完

isCancelled方法:判断计算是否被取消

这些接口的设计很完美,FutureTask的实现注定不会简单,后面再说。

3)在第三步里面,调用了isDone方法查看状态,然后直接调用task.get方法获取厨具,不过这时还没送到,所以还是会等待3秒。对比第一段代码的执行结果,这里我们节省了2秒。这是因为在快递员送货期间,我们去超市购买食材,这两件事在同一时间段内异步执行!!!

JDK8 中 CompletableFuture 是非常强大的 Future 的扩展功能。

模拟代码:

public class CompleteFutureTests {
    }
}

allOf 工厂方法接收一个由CompletableFuture 构成的数组,数组中的所有 Completable-Future 对象执行完成之后,它返回一个 CompletableFuture<Void> 对象。这意味着,如果你需要等待多个 CompletableFuture 对象执行完毕,对 allOf 方法返回的
CompletableFuture 执行 join 操作可以等待CompletableFuture执行完成。

或者你可能希望只要 CompletableFuture 对象数组中有任何一个执行完毕就不再等待,在这种情况下,你可以使用一个类似的工厂方法 anyOf