Java8 In Action-3.高效 Java 8 编程(四)

4.CompletableFuture:组合式异步编程
4.1Future 接口
java.util.concurrent.Future接口在Java 5中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作,不再需要呆呆等待耗时的操作完成。该接口表示一个异步计算(即调用线程可以继续运行,不会因为调用方法而阻塞)的结果。

/**
         * 使用Future以异步的方式执行一个耗时的操作
         * Future的另一个优点是它比更底层的Thread更易用。
         * 要使用Future,通常你只需要将耗时的操作封装在一个Callable对象中,
         * 再将它提交给ExecutorService,就万事大吉了。
         */
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<Object> future = executor.submit(new Callable<Object>() {
            return

            doSomeLongComputation();//以异步方式在新的线程中执行耗时的操作
        });

        doSomethingElse();//异步操作进行的同时,可以做其他的事情
        try {
            Object result = future.get(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {//当前线程在等待过程中被中断
            e.printStackTrace();
        } catch (ExecutionException e) {//计算抛出一个异常
            e.printStackTrace();
        } catch (TimeoutException e) { //在Future对象完成前超时
            e.printStackTrace();
        }

在这里插入图片描述

4.2实现异步API
将同步方法转换为异步方法
错误处理

4.3让你的代码免受阻塞之苦
使用并行流对请求进行并行操作
使用CompletableFuture 发起异步请求
使用定制的执行器

package com.h.java8;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * <br>最佳价格查询器</br>
 */
public class Shop {
    private static List<Shop> shops;

    static {
        shops = Arrays.asList(new Shop("BestPrice"),
                new Shop("LetsSaveBig"),
                new Shop("MyFavoriteShop"),
                new Shop("BuyItAll"),
                new Shop("ShopEasy"),
                new Shop("ShopColl"));
    }

    /**
     * 为“最优价格查询器”应用定制的执行器
     * 更充分地利用CPU资源
     */
    private static Executor executor = Executors.newFixedThreadPool(
            //1为了避免发生由于商店的数目过多导致服务器超负荷而崩溃,设置一个上限100
            Math.min(shops.size(), 100),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    //使用守护线程——这种方式不会阻止程序的关停
                    t.setDaemon(true);
                    return t;
                }
            }
    );

    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 getPricce(String product) {
        return calculatePrice(product);
    }

    /**
     * 将同步方法改为异步方法
     *
     * @param product
     * @return
     */
    public Future<Double> getPriceAsync(String product) {
       /*
       //创建CompletableFuture对象,它包含异步计算的结果
        CompletableFuture<Double> futurePrice = new CompletableFuture<>();
        //在另一个线程中以异步的方式执行计算
        new Thread(() -> {
            try {
                double price = calculatePrice(product);
                futurePrice.complete(price);//需长时间计算的任务结束并得出结果时,设置Future的返回值
            } catch (Exception e) {
                //错误处理,最终向客户端抛出的异常
                futurePrice.completeExceptionally(e);
            }
        }).start();
        return futurePrice;
        */

        /**
         * 使用工厂方法supplyAsync创建CompletableFuture对象(和上面的代码是等价的)
         * supplyAsync方法接受一个生产者(Supplier)作为参数,返回一个CompletableFuture对象,
         * 该对象完成异步执行后会读取调用生产者方法的返回值。生产者方法会交由ForkJoinPool池中的某个执行线程(Executor)运行
         */
        return CompletableFuture.supplyAsync(() -> calculatePrice(product));
    }

    /**
     * 价格计算器
     *
     * @param product
     * @return
     */
    private double calculatePrice(String product) {
        delay(); //模拟价格计算的时间
        return new Random().nextDouble() * product.charAt(0) + product.charAt(1); //返回一个价格的随机值
    }

    /**
     * 根据产品名称返回一个字符串列表,包含商店的名称,该商店中指定商品的价格
     *
     * @param product
     * @return
     */
    public List<String> findPrices(String product) {
       /* *//**
         * 1.采用顺序查询所有商店的方式实现的findPrices方法
         * 对这4个商店的查询是顺序进行的,并且一个查询操作会阻塞另一个,每一个操作都要花费大约1秒左右的时间计算请求商品的价格,
         * 所以该方法调用完成时间大概在4s
         *//*
        return shops.stream().map(shop -> String.format("%s price is %.2f",shop.getName(),shop.getPricce(product)))
                             .collect(Collectors.toList());*/

        /**
         * 2.使用并行流对请求进行并行操作
         *  使用并行流并行地从不同的商店获取价格
         *  测试完成时间在1s多点
         */
      /*  return shops.parallelStream().map(shop -> String.format("%s price is %.2f",shop.getName(),shop.getPricce(product)))
                .collect(Collectors.toList());*/

        /**
         * 3.使用CompletableFuture 发起异步请求
         * 为什么下面要用两个不同的流水线,而不是在同一个处理流的流水线上一个接一个地放置两个map操作?
         * 关键点:Stream的延迟特性会引起顺序执行
         */
        //使用CompletableFuture以异步方式计算每种商品 的价格
       /* List<CompletableFuture<String>> priceFutureList = shops.stream().map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is " + shop.getPricce(product)))
                .collect(Collectors.toList());
        //等待所有异步操作结束
        return priceFutureList.stream().map(CompletableFuture::join).collect(Collectors.toList());*/

        /**
         * 使用定制的执行器优化
         * supplyAsync(Supplier<U> supplier,Executor executor)
         */
        List<CompletableFuture<String>> priceFutureList = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is " + shop.getPricce(product),executor))
                .collect(Collectors.toList());
        return priceFutureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
    }

    /**
     * 模拟1秒钟延迟的方法
     */
    public static void delay() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
package com.h.java8;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class TestMain {

    public static void main(String[] args) throws IOException {
        //使用异步API进行价格查询
        Shop shop = new Shop("BestShop");
        long start = System.nanoTime();
        Future<Double> futurePrice = shop.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();
            System.out.printf("Price is %.2f%n", price);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
        System.out.println("Price returned after " + retrievalTime + " msecs");

        System.out.println("++++++++++++++++++++++");
        long start2 = System.nanoTime();
        List<String> list = shop.findPrices("my favorite product");
        /**
         * 使用并行流的CompletableFuture版本测试:
         * 在Shop.shops的数量!=4时,两者所用的时间差不多
         * 在Shop.shops的数量=4时,使用并行流(1s多点)比CompletableFuture(2s多点)快一倍
         * 并行流的CompletableFuture内部采用的是同样的通用线程池,默认都使用固定数目的线程,
         * Stream底层依赖的是线程数量固定的通用线程池
         * 具体线程数取决于Runtime.getRuntime().availableProcessors()的返回值。
         * 然而,CompletableFuture具有一定的优势,因为它允许你对执行器(Executor)进行配置,尤其是线程池的大小,
         * 让它以更适合应用需求的方式进行配置,满足程序的要求,而这是并行流API无法提供的.
         * 如何调整线程池的大小?
         * Nthreads = NCPU * UCPU * (1 + W/C)
         *  NCPU是处理器的核的数目,可以通过Runtime.getRuntime().availableProcessors()得到
         *  UCPU是期望的CPU利用率(该值应该介于0和1之间)
         *  W/C是等待时间与计算时间的比率
         */
        long retrievalTime2 = ((System.nanoTime() - start2) / 1_000_000);
        System.out.println(retrievalTime2);
        System.out.println(Runtime.getRuntime().availableProcessors());
        System.out.println(list);
    }

    public static void doSomethingElse() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

并行——使用流还是CompletableFutures?
目前为止,你已经知道对集合进行并行计算有两种方式:要么将其转化为并行流,利用map这样的操作开展工作,要么枚举出集合中的每一个元素,创建新的线程,在Completable-Future内对其进行操作。后者提供了更多的灵活性,你可以调整线程池的大小,而这能帮助你确保整体的计算不会因为线程都在等待I/O而发生阻塞。
我们对使用这些API的建议如下。
❑如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
❑反之,如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用
CompletableFuture灵活性更好,你可以像前文讨论的那样,依据等待/计算,或者W/C的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性会让我们很难判断到底什么时候触发了等待。

4.4对多个异步任务进行流水线操作

package com.h.java8;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.stream.Collectors;

/**
 * <br>最佳价格查询器</br>
 */
public class Shop {
    //商店名称
    private String name;
    //商店列表
    private static List<Shop> shops;

    static {
        shops = Arrays.asList(new Shop("BestPrice"),
                new Shop("LetsSaveBig"),
                new Shop("MyFavoriteShop"),
                new Shop("BuyItAll"),
                new Shop("ShopEasy"),
                new Shop("ShopColl"));
    }

    public Shop(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private static Executor executor = Executors.newFixedThreadPool(
            //1为了避免发生由于商店的数目过多导致服务器超负荷而崩溃,设置一个上限100
            Math.min(shops.size(), 100),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    //使用守护线程——这种方式不会阻止程序的关停
                    t.setDaemon(true);
                    return t;
                }
            }
    );

    /**
     * 以ShopName:price:DiscountCode的格式返回一个String类型的值
     * @param product
     * @return
     */
    public String getPrice(String product) {
        double price = calculatePrice(product);
        Discount.Code code = Discount.Code.values()[new Random().nextInt(Discount.Code.values().length)];
        return String.format("%s:%.2f:%s", name, price, code);
    }
    
	public double getPrice2(String product){
        return calculatePrice(product);
    }
    /**
     * 价格计算器
     * @param product
     * @return
     */
    private double calculatePrice(String product) {
        delay(); //模拟价格计算的时间
        return new Random().nextDouble() * product.charAt(0) + product.charAt(1); //返回一个价格的随机值
    }

    /**
     * 模拟1秒钟延迟的方法
     */
    public static void delay() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 根据产品名称返回一个字符串列表,包含商店的名称,该商店中指定商品的价格
     * @param product
     * @return
     */
    public List<String> findPrices(String product) {
        /**
         * 1.以最简单的方式实现使用Discount服务的findPrices方法
         */
        /*return shops.stream()
                .map(shop -> shop.getPrice(product)) //取得每个shop中商品的原始价格
                .map(Quote::parse) //在Quote对象中对shop返回的字符串进行转换
                .map(Discount::applyDiscount)
                .collect(Collectors.toList());*/
        /**
         * 2.使用CompletableFuture提供的特性,以异步方式重新实现findPrices方法
         */
        List<CompletableFuture<String>> futureList = shops.stream()
                //以异步方式取得每个shop中指定产品的原始价格
                .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor))
                //Quote对象存在时,对其返回的值进行转换
                .map(future -> future.thenApply(Quote::parse))
                //使用另一个异步任务构造期望的Future,申请折扣
                .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), executor)))
                .collect(Collectors.toList());
        //等待流中的所有Future执行完毕,并提取各自的返回值
        return futureList.stream()
                            .map(CompletableFuture::join)
                            .collect(Collectors.toList());
    }

 /**
     * 假设原始的商品价格的单位为美元,现在需要把它转为欧元
     * @param product
     * @return
     */
    public List<Double> findPrices2(String product) {
        List<CompletableFuture<Double>> futureList = shops.stream()
                .map(
                        shop -> CompletableFuture.supplyAsync(() -> shop.getPrice2("product"), executor)
                                /**
                                 * 合并两个独立的CompletableFuture对象
                                 */
                                .thenCombine(
                                        CompletableFuture.supplyAsync(() -> Shop.getRate()),
                                        (price, rate) -> price * rate
                                )

                )
                .collect(Collectors.toList());
        return futureList.stream()
      					  //调用get或者join方法只会造成阻塞,直到CompletableFuture完成才能继续往下运行
                         .map(CompletableFuture::join)
                          .collect(Collectors.toList());

    }

    public static double getRate(){
        return 1.12;
    }
}
package com.h.java8;

public class Quote {
    private final String shopName;
    private final double price;
    private final Discount.Code discountCode;

    public Quote(String shopName, double price, Discount.Code code) {
        this.shopName = shopName;
        this.price = price;
        this.discountCode = code;
    }

    /**
     * Shop.getPrice()会以ShopName:price:DiscountCode的格式返回一个String类型的值
     * @param s
     * @return
     */
    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;
    }
}
package com.h.java8;

import java.math.BigDecimal;
import java.text.DecimalFormat;

/**
 * <br>折扣服务</br>
 */
public class Discount {
    /**
     * double d = 3.1415926;
     * String result = String .format("%.2f");
     */
    private static final DecimalFormat df = new java.text.DecimalFormat("#.00");

    //枚举类型定义的折扣代码
    public enum Code {
        NONE(0), SILVER(5), 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 df.format(price * (100 - code.percentage) / 100);
        return new BigDecimal(price * (100 - code.percentage) / 100).setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    public static void delay() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
 public static void main(String[] args) {
        long start = System.nanoTime();
        Shop shop = new Shop("BestShop");
        List<String> list = shop.findPrices("my favorite product");
        System.out.println(list);
        long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
        System.out.println("Price returned after " + retrievalTime + " msecs");
        List<Double> doubleList = shop.findPrices2("product");
        System.out.println(doubleList);
    }

在这里插入图片描述
Java 8的 CompletableFuture API提供了名为thenCompose的方法,它就是专门为这一目的而设计的,thenCompose方法允许你对两个异步操作进行流水线,第一个操作完成时,将其结果作为参数传递给第二个操作。换句话说,你可以创建两个CompletableFutures对象,对第一个CompletableFuture 对象调用thenCompose , 并向其传递一个函数。当第一个CompletableFuture执行完毕后,它的结果将作为该函数的参数,这个函数的返回值是以第一个CompletableFuture的返回做输入计算出的第二个CompletableFuture对象。使用这种方式,即使Future在向不同的商店收集报价,主线程还是能继续执行其他重要的操作,比如响应UI事件。
CompletableFuture也提供了一个以Async后缀结尾的版本thenComposeAsync。通常而言,名称中不带Async的方法和它的前一个任务一样,在同一个线程中运行;而名称以Async结尾的方法会将后续的任务提交到一个线程池,所以每个任务是由不同的线程处理的。

将两个CompletableFuture 对象整合起来,无论它们是否存在依赖
上面的示例中,你对一个CompletableFuture对象调用了thenCompose方法,并向其传递了第二个CompletableFuture , 而第二个CompletableFuture 又需要使用第一个CompletableFuture的执行结果作为输入。但是,另一种比较常见的情况是,你需要将两个完全不相干的CompletableFuture对象的结果整合起来,而且你也不希望等到第一个任务完全结束才开始第二项任务。
这种情况,你应该使用thenCombine方法,它接收名为BiFunction的第二参数,这个参数定义了当两个CompletableFuture对象完成计算后,结果如何合并。同thenCompose方法一样,thenCombine方法也提供有一个Async的版本。这里,如果使用thenCombineAsync会导致BiFunction中定义的合并操作被提交到线程池中,由另一个任务以异步的方式执行。
在这里插入图片描述

利用Java 7的方法合并两个Future对象:

ExecutorService executor = Executors.newCachedThreadPool();
final Future<Double> futureRate = executor.submit(new Callable<Double>() {
public Double call() {
return exchangeService.getRate(Money.EUR, Money.USD);
}});
Future<Double> futurePriceInUSD = executor.submit(new Callable<Double>() {
public Double call() {
double priceInEUR = shop.getPrice(product);
return priceInEUR * futureRate.get();
}});

4.5响应CompletableFuture 的completion 事件
之前的所有示例代码都是通过在响应之前添加1秒钟的等待延迟模拟方法的远程调用.
现在的场景:你希望购买的商品在某些商店的查询速度要比另一些商店更快.

package com.h.java8;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.stream.Stream;

/**
 * <br>最佳价格查询器</br>
 */
public class Shop {
    //商店名称
    private String name;
    //商店列表
    private static List<Shop> shops;

    static {
        shops = Arrays.asList(new Shop("BestPrice"),
                new Shop("LetsSaveBig"),
                new Shop("MyFavoriteShop"),
                new Shop("BuyItAll"),
                new Shop("ShopEasy"),
                new Shop("ShopColl"));
    }

    public Shop(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private static Executor executor = Executors.newFixedThreadPool(
            //1为了避免发生由于商店的数目过多导致服务器超负荷而崩溃,设置一个上限100
            Math.min(shops.size(), 100),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    //使用守护线程——这种方式不会阻止程序的关停
                    t.setDaemon(true);
                    return t;
                }
            }
    );

    /**
     * 以ShopName:price:DiscountCode的格式返回一个String类型的值
     * @param product
     * @return
     */
    public String getPrice(String product) {
        double price = calculatePrice(product);
        Discount.Code code = Discount.Code.values()[new Random().nextInt(Discount.Code.values().length)];
        return String.format("%s:%.2f:%s", name, price, code);
    }

    /**
     * 价格计算器
     * @param product
     * @return
     */
    private double calculatePrice(String product) {
        randomDelay(); //模拟价格计算的时间
        return new Random().nextDouble() * product.charAt(0) + product.charAt(1); //返回一个价格的随机值
    }

    /**
     *  重构findPrices方法返回一个由Future构成的流
     */
    public Stream<CompletableFuture<String>> findPricesStream(String product){
        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
                                    )
                            )
                    );
    }


    /**
     *目前为止,你实现的findPrices方法只有在取得所有商店的返回值时才显示商品的价格。
     *而你希望的效果是,只要有商店返回商品价格就在第一时间显示返回值,不再等待那些还未返回的商店(有些甚至会发生超时)
     * @param product
     * @return
     */
    public List<String> findPrices(String product) {
        return null;
    }

    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);
        }
    }
}
 public static void main(String[] args) {
        Shop shop = new Shop("BestPrice");
        /**
         * 响应CompletableFuture的completion事件
         * 优化最佳价格查询器
         */
        CompletableFuture[] futures = shop.findPricesStream("myPhone")
                .map(f -> f.thenAccept(System.out::println))
                .toArray(size -> new CompletableFuture[size]);
        /**
         * 等待最初Stream中的所有CompletableFuture对象执行完毕,
         * 对allOf方法返回的CompletableFuture执行join操作
         */
        CompletableFuture.allOf(futures).join();
        System.out.println("All shops returned results or timed out");
        /**
         * 然而在另一些场景中,你可能希望只要CompletableFuture对象数组中有任何一个执行完毕就不再等待,
         * 比如,你正在查询两个汇率服务器,任何一个返回了结果都能满足你的需求。
         * 在这种情况下,你可以使用一个类似的工厂方法anyOf。该方法接收一个CompletableFuture对象构成的数组,
         * 返回由第一个执行完毕的CompletableFuture对象的返回值构成的Completable-Future<Object>。
         */
    }
 public static void main(String[] args) {
        Shop shop = new Shop("BestPrice");
        /**
         * 输出中打印每个价格计算所消耗的时间
         */
        long start = System.nanoTime();
        CompletableFuture[] futures = shop.findPricesStream("myPhone")
                .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");
        /**
         * 我们看到,由于随机延迟的效果,第一次价格查询比最慢的查询要快近两倍。
         *   BuyItAll price is 115.84 (done in 1733msecs)
             BestPrice price is 163.68 (done in 1757msecs)
             ShopEasy price is 137.47 (done in 2469msecs)
             ShopColl price is 206.65 (done in 2549msecs)
             MyFavoriteShop price is 140.87 (done in 3454msecs)
             LetsSaveBig price is 121.17 (done in 3495msecs)
             All shops have now responded in 3495 msecs)
         */
    }

小结:
 执行比较耗时的操作时,尤其是那些依赖一个或多个远程服务的操作,使用异步任务可以改善程序的性能,加快程序的响应速度。
 你应该尽可能地为客户提供异步API。使用CompletableFuture类提供的特性,你能够轻松地实现这一目标。
 CompletableFuture类还提供了异常管理的机制,让你有机会抛出/管理异步任务执行中发生的异常。
 将同步API的调用封装到一个CompletableFuture中,你能够以异步的方式使用其结果。
 如果异步任务之间相互独立,或者它们之间某一些的结果是另一些的输入,你可以将这些异步任务构造或者合并成一个。
 你可以为CompletableFuture注册一个回调函数,在Future执行完毕或者它们计算的结果可用时,针对性地执行一些程序。
 你可以决定在什么时候结束程序的运行,是等待由CompletableFuture对象构成的列表中所有的对象都执行完毕,还是只要其中任何一个首先完成就中止程序的运行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值