https://github.com/chenfangya/Demo.git
Future 接口
Java 8之前使用Future的例子:
@Test
public void test1() {
ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(new Callable<Double>() {//向ExecutorService提交一个Callable对象
public Double call() {
return doSomeLongComputation();//以异步方式在新的线程中执行耗时的操作
}
});
doSomethingElse();//异步操作进行的同时,你可以做其他的事情
try {
//获取异步操作的结果, 如果最终被阻塞, 无法得到结果, 那么在最多等待1秒钟之后退出
Double result = future.get(1, TimeUnit.SECONDS);
} catch (ExecutionException ee) {
// 计算抛出一个异常
} catch (InterruptedException ie) {
// 当前线程在等待过程中被中断
} catch (TimeoutException te) {
// 在Future对象完成之前超过已过期
}
}
public Double doSomeLongComputation() {
System.out.println("doSomeLongComputation");
try {
Thread.sleep(5000);
System.out.println("doSomeLongComputation------------END");
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1.0;
}
public void doSomethingElse() {
System.out.println("doSomethingElse");
try {
Thread.sleep(2000);
System.out.println("doSomethingElse------------END");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
使用 CompletableFuture 构建异步应用
public double getPrice(String product) {
return calculatePrice(product);
}
private double calculatePrice(String product) {
delay();
Random random = new Random();
return random.nextDouble() * product.charAt(0) + product.charAt(1);
}
/**
模拟1秒钟延迟的方法
*/
public static void delay() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
调用上述方法时,它会被阻塞。为等待同步事件完成而等待1秒钟(同步)
改进 (将同步方法转换为异步方法):
将getPrice转换为getPriceAsync方法,并修改它的返回值
public Future<Double> getPriceAsync(String product) {
CompletableFuture<Double> futurePrice = new CompletableFuture<>();
new Thread( () -> {//在另一个线程中以异步方式执行计算
double price = calculatePrice(product);
futurePrice.complete(price);//需长时间计算的任务结束并得出结果时,设置Future的返回值
}).start();
return futurePrice;//无需等待还没结束的计算,直接返回Future对象
}
测试:
@Test
public void test2() throws Exception {
long start = System.nanoTime();
Future<Double> futurePrice = getPriceAsync("my favorite product");//试图取得价格
long invocationTime = ((System.nanoTime() - start) / 1_000_000);
System.out.println("Invocation returned after " + invocationTime + " msecs");
// 执行更多任务
doSomethingElse();
// 在计算商品价格的同时
try {
double price = futurePrice.get();//从Future对象中读取价格,如果价格未知,会发生阻塞
System.out.printf("Price is %.2f%n", price);
} catch (Exception e) {
throw new RuntimeException(e);
}
long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
System.out.println("Price returned after " + retrievalTime + " msecs");
}
错误处理
抛出CompletableFuture内的异常
public Future<Double> getPriceAsync(String product) {
CompletableFuture<Double> futurePrice = new CompletableFuture<>();
new Thread( () -> {
try {
double price = calculatePrice(product);
futurePrice.complete(price);//如果价格计算正常结束,完成Future操作并设置商品价格
} catch (Exception ex) {
futurePrice.completeExceptionally(ex);//否则就抛出导致失败的ExecutionException异常,完成这次Future操作
}
}).start();
return futurePrice;
}
客户端现在会收到一个ExecutionException异常,该异常接收了一个包含失败原因的Exception参数.
使用工厂方法supplyAsync创建CompletableFuture
public Future<Double> getPriceAsync(String product) {
return CompletableFuture.supplyAsync(() -> calculatePrice(product));
}
使用并行流对请求进行并行操作
普通操作:
public List<String> findPrices(String product) {
List<String> shops = Arrays.asList("BestPrice", "LetsSaveBig", "MyFavoriteShop", "BuyItAll");
return shops.stream().map(shop -> String.format("%s price is %.2f", shop, getPrice(product)))
.collect(Collectors.toList());
}
使用并行流:
public List<String> findPricesParallelStream(String product) {
List<String> shops = Arrays.asList("BestPrice", "LetsSaveBig", "MyFavoriteShop", "BuyItAll");
return shops.parallelStream().map(shop -> String.format("%s price is %.2f", shop, getPrice(product)))
.collect(Collectors.toList());
}
测试:
@Test
public void test3() {
long start = System.nanoTime();
//System.out.println(findPrices("myPhone27S"));Done in 4078msecs
System.out.println(findPricesParallelStream("myPhone27S"));//Done in 1055 msecs
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Done in " + duration + " msecs");
}
使用CompletableFuture实现findPrices方法:
public List<String> findPrices2(String product) {
List<String> shops = Arrays.asList("BestPrice", "LetsSaveBig", "MyFavoriteShop", "BuyItAll");
List<CompletableFuture<String>> priceFutures =
shops.stream()
.map(shop -> CompletableFuture.supplyAsync(//使用CompletableFuture以异步方式计算每种商品的价格
() -> String.format("%s price is %.2f",
shop, getPrice(product))))
.collect(Collectors.toList());
return priceFutures.stream()
.map(CompletableFuture::join)//等待所有异步操作结束
.collect(Collectors.toList());
}
这里使用了两个不同的Stream流水线,而不是在同一个处理流的流水线上一个接一个地放置两个map操作,是考虑流操作之间的延迟特性,如果你在单一流水线中处理流,请求只能以同步、顺序执行的方式才会成功
@Test
public void test3() {
long start = System.nanoTime();
System.out.println(findPrices2("myPhone27S"));
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Done in " + duration + " msecs");//Done in 2005 msecs
}
超过2秒意味着利用CompletableFuture实现的版本,比findPrices()方法快,但是它的用时也差不多是使用并行流的前一个版本的两倍。
list长度+1;
List<String> shops = Arrays.asList("BestPrice", "LetsSaveBig", "MyFavoriteShop", "BuyItAll", "ShopEasy");
findPricesParallelStream(); //Done in 2065 msecs
findPrices2();//Done in 2059 msecs
看起来不相伯仲
究其原因都一样:它们内部采用的是同样的通用线程池,默认都使用固定数目的线程,具体线程数取决于Runtime.getRuntime().availableProcessors()的返回值。
但是, CompletableFuture允许对执行器(Executor)进行配置,尤其是线程池的大小,让它以更适合应用需求的方式进行配置,满足程序的要求。
使用定制的执行器
调整线程池的大小
《Java并发编程实战》 Brian Goetz建议,线程池大小与处理器的利用率之比可以使用下面的公式进行估算:
Nthreads = NCPU * UCPU * (1 + W/C)
其中:
❑NCPU是处理器的核的数目,可以通过Runtime.getRuntime().availableProcessors()得到
❑UCPU是期望的CPU利用率(该值应该介于0和1之间)
❑W/C是等待时间与计算时间的比率
应用定制的执行器
public List<String> findPrices3(String product) {
List<String> shops = Arrays.asList("BestPrice", "LetsSaveBig", "MyFavoriteShop", "BuyItAll", "ShopEasy");
final Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100), //创建一个线程池,线程池中线程的数 目为 100和商店数目二者中较小的一个值
new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);//使用守护线程——这种方式不会阻止程序的关停
return t;
}
});
List<CompletableFuture<String>> priceFutures = shops.stream().map(
shop -> CompletableFuture.supplyAsync(() -> String.format("%s price is %.2f", shop, getPrice(product)), executor))
.collect(Collectors.toList());
return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
测试:
@Test
public void test3() {
long start = System.nanoTime();
System.out.println(findPrices3("myPhone27S"));
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Done in " + duration + " msecs");//Done in 1056 msecs
}
改进之后,使用CompletableFuture方案的程序处理5个商店仅耗时1056 毫秒。
并行——使用流还是CompletableFutures?
1.如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
2.反之,如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好,你可以像前文讨论的那样,依据等待/计算,或者W/C的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性会让我们很难判断到底什么时候触发了等待。
对多个异步任务进行流水线操作
假设所有的商店都同意使用一个集中式的折扣服务。该折扣服务提供了五个不同的折扣代码,每个折扣代码对应不同的折扣率。你使用一个枚举型变量Discount.Code来实现这一想法,具体代码如下所示
public class Discount {
public enum Code {
NONE(0), SILVER(0), GOLD(10), PLATINUM(15), DIAMOND(20);
private final int percentage;
Code(int percentage) {
this.percentage = percentage;
}
}
public static String applyDiscount(Quote quote) {
return quote.getShopName() + " price is " + Discount.apply(quote.getPrice(), quote.getDiscountCode());
}
private static double apply(double price, Code code) {
delay();
return price * (100 - code.percentage) / 100;
}
/**
* 模拟计算,查询数据库等耗时
*/
public static void delay() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public final class Quote {
private final String shopName;
private final double price;
private final Discount.Code discountCode;
public Quote(String shopName, double price, Discount.Code discountCode) {
this.shopName = shopName;
this.price = price;
this.discountCode = discountCode;
}
public static Quote parse(String s) {
String[] split = s.split(":");
String shopName = split[0];
double price = Double.parseDouble(split[1]);
Discount.Code discountCode = Discount.Code.valueOf(split[2]);
return new Quote(shopName, price, discountCode);
}
public String getShopName() {
return shopName;
}
public double getPrice() {
return price;
}
public Discount.Code getDiscountCode() {
return discountCode;
}
}
public class Shop {
private final String name;
private final Random random;
private static final DecimalFormat formatter = new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.US));
public Shop(String name) {
this.name = name;
random = new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));
}
public String getPrice(String product) {
double price = calculatePrice(product);
Discount.Code code = Discount.Code.values()[random.nextInt(Discount.Code.values().length)];
return name + ":" + price + ":" + code;
}
public double calculatePrice(String product) {
delay();
return format(random.nextDouble() * product.charAt(0) + product.charAt(1));
}
public String getName() {
return name;
}
public static void delay() {
int delay = 1000;
//int delay = 500 + RANDOM.nextInt(2000);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static double format(double number) {
synchronized (formatter) {
return new Double(formatter.format(number));
}
}
}
使用 Discount 服务
public List<String> findPrices5(String product) {
List<Shop> shops = Arrays.asList(new Shop("BestPrice"), new Shop("LetsSaveBig"), new Shop("MyFavoriteShop"),
new Shop("BuyItAll"), new Shop("ShopEasy"));
return shops.stream()
.map(shop -> shop.getPrice(product))//取得每个shop对象中商品的原始价格
.map(Quote::parse)//在 Quote 对 象 中对shop返回的字符串进行转换
.map(Discount::applyDiscount)//联系Discount服务,为每个Quote申请折扣
.collect(Collectors.toList());
}
@Test
public void test4() {
long start = System.nanoTime();
System.out.println(findPrices5("myPhone27S"));
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Done in " + duration + " msecs");
}
[BestPrice price is 110.934, LetsSaveBig price is 135.576, MyFavoriteShop price is 192.717, BuyItAll price is 184.74, ShopEasy price is 176.08]
Done in 10052 msecs
- 第一个操作将每个shop对象转换成了一个字符串,该字符串包含了该 shop中指定商品的价格和折扣代码。
- 第二个操作对这些字符串进行了解析,在Quote对象中对它们进行转换。
- 最终,第三个map会操作联系远程的Discount服务,计算出最终的折扣价格,并返回该价格及提供该价格商品的shop。
这次执行耗时10秒,因为顺序查询5个商店耗时大约5秒, 现在又加上了Discount服务为5个商店返回的价格申请折扣所消耗的5秒钟。
构造同步和异步操作
public List<String> findPrices6(String product) {
List<Shop> shops = Arrays.asList(new Shop("BestPrice"), new Shop("LetsSaveBig"), new Shop("MyFavoriteShop"),
new Shop("BuyItAll"), new Shop("ShopEasy"));
final Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100), // 创建一个线程池,线程池中线程的数 目为
// 100和商店数目二者中较小的一个值
new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);// 使用守护线程——这种方式不会阻止程序的关停
return t;
}
});
List<CompletableFuture<String>> priceFutures = shops.stream()
.map(shop -> CompletableFuture.supplyAsync(//以异步方式取得每个shop中指定产品的原始价格
() -> shop.getPrice(product), executor))
.map(future -> future.thenApply(Quote::parse))//Quote对象存在时,对其返回的值进行转换
.map(future -> future.thenCompose(//使用另一个异步任务构造期望的Future,申请折扣
quote -> CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor)))
.collect(Collectors.toList());
return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());//等待流中的所有Future执行 完毕,并提取各自的返回值
}
findPrices6()使用CompletableFuture类提供的特性,在需要的地方把它们变成了异步操作。
-
获取价格
这三个操作中的第一个只需要将Lambda表达式作为参数传递给supplyAsync工厂方法就可以以异步方式对shop进行查询。第一个转换的结果是一个Stream<CompletableFuture>,一旦运行结束,每个CompletableFuture对象中都会包含对应shop返回的字符串。 -
解析报价
现在需要进行第二次转换将字符串转变为订单。由于一般情况下解析操作不涉及任何远程服务,也不会进行任何I/O操作,它几乎可以在第一时间进行,所以能够采用同步操作,不会带来太多的延迟。由于这个原因,你可以对第一步中生成的CompletableFuture对象调用它的thenApply,将一个由字符串转换Quote的方法作为参数传递给它(直到调用的CompletableFuture执行结束,使用的thenApply方法都不会阻塞你代码的执行) -
为计算折扣价格构造Future
第三个map操作涉及联系远程的Discount服务,为从商店中得到的原始价格申请折扣率。这一转换与前一个转换又不大一样,因为这一转换需要远程执行(或者,就这个例子而言,它需要模拟远程调用带来的延迟),出于这一原因,希望它能够异步执行。
为了实现这一目标,我们像第一个调用传递getPrice给supplyAsync那样,将这一操作以Lambda表达式的方式传递给了supplyAsync工厂方法,该方法最终会返回另一个CompletableFuture对象。到目前为止,你已经进行了两次异步操作,用了两个不同的CompletableFutures对象进行建模,你希望能把它们以级联的方式串接起来进行工作
从shop对象中获取价格,接着把价格转换为Quote。
拿到返回的Quote对象,将其作为参数传递给Discount服务,取得最终的折扣价格。
Java 8的 CompletableFuture API提供了名为thenCompose的方法,它就是专门为这一目的而设计的, thenCompose方法允许你对两个异步操作进行流水线,第一个操作完成时,将其结果作为参数传递给第二个操作。
就是你可以创建两个CompletableFutures对象,对第 一 个 CompletableFuture 对 象 调 用 thenCompose , 并 向其 传 递一 个函 数 。当 第一 个CompletableFuture执行完毕后,它的结果将作为该函数的参数,这个函数的返回值是以第一个CompletableFuture的返回做输入计算出的第二个CompletableFuture对象。使用这种方式,即使Future在向不同的商店收集报价,主线程还是能继续执行其他重要的操作,比如响应UI事件。
将这三次map操作的返回的Stream元素收集到一个列表,你就得到了一个List<CompletableFuture>,等这些CompletableFuture对象最终执行完毕,就可以利用join取得它们的返回值。
@Test
public void test5() {
long start = System.nanoTime();
System.out.println(findPrices6("myPhone27S"));
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Done in " + duration + " msecs");
}
[BestPrice price is 110.934, LetsSaveBig price is 135.576, MyFavoriteShop price is 192.717, BuyItAll price is 184.74, ShopEasy price is 176.08]
Done in 2056 msecs
将两个 CompletableFuture 对象整合起来,无论它们是否存在依赖 thenCombine方法
同thenCompose方法一样,thenCombine方法也提供有一个Async的版本。这里,如果使用thenCombineAsync会导致BiFunction中定义的合并操作被提交到线程池中,由另一个任务以异步的方式执行。
例:
有一家商店提供的价格是以欧元(EUR)计价的,但是你希望以美元的方式提供给你的客户。你可以用异步的方式向商店查询指定商品的价格,同时从远程的汇率服务那里查到欧元和美元之间的汇率。当二者都结束时,再将这两个结果结合起来,用返回的商品价格乘以当时的汇率,得到以美元计价的商品价格。
public class Shop2 {
private final String name;
private final Random random;
public Shop2(String name) {
this.name = name;
random = new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));
}
public double getPrice(String product) {
return calculatePrice(product);
}
private double calculatePrice(String product) {
delay();
return random.nextDouble() * product.charAt(0) + product.charAt(1);
}
public Future<Double> getPriceAsync(String product) {
CompletableFuture<Double> futurePrice = new CompletableFuture<>();
new Thread( () -> {
double price = calculatePrice(product);
futurePrice.complete(price);
}).start();
return futurePrice;
}
public String getName() {
return name;
}
public static void delay() {
int delay = 1000;
//int delay = 500 + RANDOM.nextInt(2000);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public List<String> findPrices7(String product) {
List<Shop2> shops = Arrays.asList(new Shop2("BestPrice"), new Shop2("LetsSaveBig"), new Shop2("MyFavoriteShop"),
new Shop2("BuyItAll"), new Shop2("ShopEasy"));
List<CompletableFuture<Double>> priceFutures = new ArrayList<>();
for (Shop2 shop : shops) {
CompletableFuture<Double> futurePriceInUSD =
CompletableFuture.supplyAsync(() -> shop.getPrice(product))/创建第一个任务查询商店取得商品的价格
.thenCombine(
CompletableFuture.supplyAsync(
() -> ExchangeService.getRate(Money.EUR, Money.USD)),//创建第二个独立任务,查询美元和欧元之间的转换汇率
(price, rate) -> price * rate//通过乘法整合得到的商品价格和汇率
);
priceFutures.add(futurePriceInUSD);
}
List<String> prices = priceFutures
.stream()
.map(CompletableFuture::join)
.map(price -> /*shop.getName() +*/ " price is " + price)
.collect(Collectors.toList());
return prices;
}
响应 CompletableFuture 的 completion 事件
模拟生成0.5秒至2.5秒
private static final Random random = new Random();
public static void randomDelay() {
int delay = 500 + random.nextInt(2000);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
重构findPrices方法返回一个由Future构成的流
public Stream<CompletableFuture<String>> findPricesStream(String product) {
List<Shop> shops = Arrays.asList(new Shop("BestPrice"), new Shop("LetsSaveBig"), new Shop("MyFavoriteShop"),
new Shop("BuyItAll"), new Shop("ShopEasy"));
final Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100), // 创建一个线程池,线程池中线程的数 目为
// 100和商店数目二者中较小的一个值
new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);// 使用守护线程——这种方式不会阻止程序的关停
return t;
}
});
return shops.stream()
.map(shop -> CompletableFuture.supplyAsync(
() -> shop.getPrice(product), executor))
.map(future -> future.thenApply(Quote::parse))
.map(future -> future.thenCompose(quote ->
CompletableFuture.supplyAsync(
() -> Discount.applyDiscount(quote), executor))
);
}
响应CompletableFuture的completion事件
@Test
public void test9() {
long start = System.nanoTime();
CompletableFuture[] futures = findPricesStream("myPhone27S")
.map(f -> f.thenAccept(s -> System.out
.println(s + " (done in " + ((System.nanoTime() - start) / 1_000_000) + " msecs)")))
.toArray(size -> new CompletableFuture[size]);
CompletableFuture.allOf(futures).join();
System.out.println("All shops have now responded in " + ((System.nanoTime() - start) / 1_000_000) + " msecs");
}
BestPrice price is 110.934 (done in 2063 msecs)
BuyItAll price is 184.74 (done in 2063 msecs)
LetsSaveBig price is 135.576 (done in 2063 msecs)
ShopEasy price is 176.08 (done in 2063 msecs)
MyFavoriteShop price is 192.717 (done in 2063 msecs)
All shops have now responded in 2064 msecs
allOf工厂方法接收一个由CompletableFuture构成的数组,数组中的所有CompletableFuture对象执行完成之后,它返回一个CompletableFuture对象