1. Future
Future模式是从JDK5引入的,Future接口是其的实现。Future实现在JUC包中,用来进行异步计算意图简化并发计算的开发工作量。
Future的接口提供了五个方法。
public interface Future<V> {
//取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束
boolean cancel(boolean mayInterruptIfRunning);
//判断任务是否已经取消,任务正常完成前将其取消,则返回 true
boolean isCancelled();
//判断任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true
boolean isDone();
//等待任务执行结束,然后获得V类型的结果。
V get() throws InterruptedException, ExecutionException;
//获得计算结果,加入了超时时间设置
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Future工作原理如图所示。任务提交给Future后,Future替我完成这个任务。期间我可以去做其他事情,计算完成后从Future那取出整合结果即可。
我们这边举一个简单的例子演示一下Future的合并结果的功能。假设:计算累加求和。可以将这个问题分解为求和两个子计算。这两个子计算可以并行处理,最后再把两个子计算的结果合并。理论上,计算时间可以节省一半左右。
Future<Integer> future1 = executor.submit(()->1+2);
Future<Integer> future2 = executor.submit(()->3+4);
int result = future1.get()+future2.get();
Future的局限性:
Future只提供了一种获取计算结果的方法,那就是get(),这是一个阻塞方法。 所以Future适用于将一个任务分解为多个不相关的、并行处理的子任务。最后采用阻塞的方式,从所有子任务获取计算结果再汇总。
2. CompletableFuture
CompletableFuture是Future的子类。它除了get()方法外,还定义了诸多异步方式传递操作结果的方法。如果说普通Future的适用于拆分任务,减少计算时间。那么CompletableFuture适用于:协调组织NIO、异步消息的交互过程,减少线程资源占用。
-
在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合
CompletableFuture 的方法。 -
它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage
),它支持在计算完成以后触发一些函数或执行某些动作。 -
它实现了Future和CompletionStage接口。
CompletableFuture——小窍门
-
所有thenXXX都只能处理正确的结果。whenComplete和handler可以处正确和错误的结果
-
如果方法名中有accpet,例如thenAccept。那么表示只能接收结果,不能返回新的结果
-
如果方法名中有apply,例如thenApply。那么表示可以返回新的结果
如果方法名中有run,例如thenRun。表示即不能接收结果,也不能返回结果。只是在合适的时机被触发。
实例演示
为了实现最佳价格查询器应用,让我们从每个商店都应该提供的API定义入手。首先,商店应该声明依据指定产品名称返回价格的方法。同时我们查询价格的方法实现了阻塞和异步来进行对比。
package com.lyc.completablefuture;
import java.util.concurrent.CompletableFuture;
/**
* <p>文件名称:Shop</p>
* <p>文件描述:每个商店都提供的对外访问的API </p>
* <p>完成日期:2021年07年12日 17:30:58</p>
*
* @author luoyongchang
*/
public class Shop {
/**
* 商店名称
*/
private String name;
public Shop(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* (阻塞式) 通过名称查询价格
*
* @param product
* @return
*/
public double getPrice(String product) {
return calculatePrice(product);
}
/**
* 计算价格
*
* @param product
* @return
*/
private double calculatePrice(String product) {
delay();
return 10 * product.charAt(0);
}
/**
* 模拟耗时操作,阻塞1秒
*/
private void delay() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 使用静态工厂supplyAsync(非阻塞式)异步获取价格
*
* @param product
* @return
*/
public CompletableFuture<Double> getPriceAsync(String product) {
//无需等待还没结束的计算,直接返回Future对象
return CompletableFuture.supplyAsync(() -> calculatePrice(product));
}
}
接下来我们开始编写测试类调用上面的API服务实现我们的商品比价器:
package com.lyc.completablefuture;
import org.junit.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* <p>文件名称:FutureTestDemo</p>
* <p>文件描述:测试类 </p>
* <p>完成日期:2021年07年12日 17:12:14</p>
*
* @author luoyongchang
*/
public class FutureTestDemo {
/**
* 测试同步API
*/
@Test
public void testGetPrice(){
Shop shop = new Shop("Best Shop");
long start = System.nanoTime();
double price = shop.getPrice("mac book pro");
System.out.printf(shop.getName()+" Price is %.2f%n",price);
long invocationTime = (System.nanoTime()-start)/1_000_000;
System.out.println("同步方法调用花费时间:--- "+invocationTime+" --- msecs");
//...其他操作
doSomethingElse();
long retrievalTime = (System.nanoTime()-start)/1_000_000;
System.out.println("同步方法返回价格所需时间: --- "+retrievalTime+" ---msecs");
}
/**
* 测试异步API
*/
@Test
public void testAsync(){
Shop shop = new Shop("Best Shop");
long start = System.nanoTime();
CompletableFuture<Double> futurePrice = shop.getPriceAsync("mac book pro");
long invocationTime = (System.nanoTime()-start)/1_000_000;
System.out.println("异步方法调用花费时间: --- "+invocationTime+" --- msecs");
//...其他操作
doSomethingElse();
//从future对象中读取价格,如果价格未知,则发生阻塞.
try {
Double price = futurePrice.get();
System.out.printf(shop.getName()+" Price is %.2f%n",price);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
long retrievalTime = (System.nanoTime()-start)/1_000_000;
System.out.println("异步方法返回价格所需时间: --- "+retrievalTime+" ---msecs");
}
/**
* 其它操作
*/
public static void doSomethingElse(){
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试结果:
总结:可以明显看到使用CompletableFuture来实现异步编程,与传统的同步相比可以大幅度提高运行速度。