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方法里面。
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; }
Callable接口可以看作是Runnable接口的补充,call方法带有返回值,并且可以抛出异常。
2)把Callable实例当作参数,生成一个FutureTask的对象,然后把这个对象当作一个Runnable,作为参数另起线程。
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
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; }
这个继承体系中的核心接口是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 {
public static void main(String[] args) throws Exception {
//1
CompletableFuture<String> bookFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("000000000");
return "xanyi000001";
});
//2
CompletableFuture<String> tableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("沉睡5秒");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "xanyi111110";
});
//CompletableFuture.allOf(bookFuture, tableFuture).join();
CompletableFuture.anyOf(bookFuture, tableFuture).join();
System.out.println("book -> " + bookFuture.get());
System.out.println("tale -> " + tableFuture.get());
}
}
allOf 工厂方法接收一个由CompletableFuture 构成的数组,数组中的所有 Completable-Future 对象执行完成之后,它返回一个 CompletableFuture<Void> 对象。这意味着,如果你需要等待多个 CompletableFuture 对象执行完毕,对 allOf 方法返回的
CompletableFuture 执行 join 操作可以等待CompletableFuture执行完成。
或者你可能希望只要 CompletableFuture 对象数组中有任何一个执行完毕就不再等待,在这种情况下,你可以使用一个类似的工厂方法 anyOf 。