Java 8系列之Future

Java 8系列:
Java 8系列之Lambda表达示
Java 8系列之StreamApi
Java 8系列之Collector
Java 8系列之Optional
Java 8系列之Future

一.基本概念

1.并发与并行

2.同步API与异步API

同步API:你调用了某个方法,调用方在被调用方运行的过程中会等待,被调用方运行结束返回,调用方取得被调用方的返回值并继续运行。即使调用方和被调用方在不同的线程中运行,调用方还是需要等待被调用方结束运行,这就是阻塞式调用。
异步API:你调用了某个方法,被调用方直接返回,或者至少在被调用方计算完成之前,将它剩余的计算任务交给另一个线程去做,该线程和调用方是异步的,这就是非阻塞式调用。

二.异步调用

1.java8之前实现异步调用的方式

首先写一个简单的例子来了解异步调用:使用Future以异步的方式执行一个耗时的操作,这种编程方式让我们的线程可以在ExecutorService以并发方式调用另一个线程执行耗时操作的同时,去执行一些其他的任务。

//创建Executor-Service ,通过它你可以向线程池提交任务
    ExecutorService executor = Executors.newCachedThreadPool();
    @Test
    public void test1() {
        long start = System.nanoTime();
        //向 Executor-Service 提交一个Callable 对象
        Future<Double> future = executor.submit(new Callable<Double>() {
            public Double call() {
                //以异步方式在新的线程中执行耗时的操作
                return doSomeLongComputation(start);
            }
        });
        //异步操作进行的同时,你可以做其他的事情
        doSomethingElse(start);
        try {
            //获取异步操作的结果,如果最终被阻塞,无法得到结果,那么在最多等待1秒钟之后退出
            Double result = future.get(1, TimeUnit.SECONDS);
            System.out.println("全部计算完成,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
        } catch (ExecutionException ee) {
            // 计算抛出一个异常
        } catch (InterruptedException ie) {
            // 当前线程在等待过程中被中断
        } catch (TimeoutException te) {
            // 在Future对象完成之前超过已过期
        }
    }
    
    public double doSomeLongComputation(Long start) {
        delay();
        System.out.println("异步执行一个长的计算,耗时:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
        return 0.00;
    }
    public void doSomethingElse(Long start) {
        delay();
        System.out.println("当前线程做别的计算,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
    }
    
    public static void delay() {
        try {
            Thread.sleep( (long) (Math.random() * 1000));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

结果:

2.使用java8的CompletableFuture来实现异步调用

 private double calculatePrice(String product) {
        delay();
        return new Random().nextDouble() * product.charAt(0) + product.charAt(1);
    }

    public Future<Double> getPriceAsync(String product) {
        //创建 CompletableFuture对象,它会包含计算的结果
        CompletableFuture<Double> futurePrice = new CompletableFuture<>();
        //在另一个线程中以异步方式执行计算
        new Thread( () -> {
            System.out.println("异步做别的计算");
            try {
                //如果价格计算正常结束,完成 Future 操作并设置商品价格
                double price = calculatePrice(product);
                //需长时间计算的任务结束并得出结果时,设置Future 的返回值
                futurePrice.complete(price);

            } catch (Exception ex) {
                //否则就抛出导致失败的异常,完成这次 Future 操作
                futurePrice.completeExceptionally(ex);
            }
        }).start();
        //无需等待还没结束的计算,直接返回 Future 对象
        return futurePrice;
    }
        public double getPriceDirect(Long start,String product) {
        double price = calculatePrice(product);
        System.out.println("当前线程去查询羽毛球拍的价格,耗时:"+ (System.nanoTime() - start) / 1_000_000 + " msecs");
        return price;
    }
    @Test
    public void test2() {
        long start = System.nanoTime();
        Future<Double> futurePrice = getPriceAsync("羽毛球");
        long invocationTime = ((System.nanoTime() - start) / 1_000_000);
        System.out.println("异步去查询羽毛球的价格,耗时: " + invocationTime + " msecs");
        // 执行更多任务,比如查询其他商店
        double priceDirect = getPriceDirect(start, "羽毛球拍");
        // 在计算商品价格的同时
        try {
            double priceAsync = futurePrice.get();
            System.out.printf("羽毛球跟羽毛球拍的总价格是: %.2f%n", priceAsync+priceDirect);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
        System.out.println("Price returned after " + retrievalTime + " msecs");
    }

结果:

其中CompletableFuture类自身提供了大量精巧的工厂方法,使用这些方法能更容易地完成整个流程,还不用担心实现的细节。
比如,采用supplyAsync方法后,可以用一行语句重写代码清单11中的getPriceAsync方法,如下所示。

    //使用工厂方法 supplyAsync 创建 CompletableFuture 对象
    public Future<Double> getPriceAsync(String product) {
        return CompletableFuture.supplyAsync(() -> calculatePrice(product));
    }

supplyAsync方法接受一个生产者(Supplier)作为参数,返回一个CompletableFuture对象,该对象完成异步执行后会读取调用生产者方法的返回值。生产者方法会交由ForkJoinPool池中的某个执行线程(Executor)运行,但是也可以使用supplyAsync方法的重载版本,传递第二个参数指定不同的执行线程执行生产者方法。一般而言,向CompletableFuture的工厂方法传递可选参数,指定生产者方法的执行线程是可行的。

三.比较顺序执行,并行,并发–异步执行,并发–自定义异步执行的速度

 public double getPrice(String product) {
     return calculatePrice(product);
 }
 List<String> shopNames = Arrays.asList("北京华联",
         "华润",
         "沃尔玛",
         "大润发",
         "万果园",
         "一峰");

 /**
  * 使用流顺序计算
  * @param product
  * @return
  */
 public List<String> findPrices(String product) {
     return shopNames.stream()
             .map(shopName -> String.format("%s 价格: %.2f",
                     shopName, getPrice(product)))
             .collect(toList());
 }

 /**
  * 使用流并行计算
  * @param product
  * @return
  */
 public List<String> findPricesParallel(String product) {
     return shopNames.parallelStream()
             .map(shopName -> String.format("%s 价格: %.2f",
                     shopName, getPrice(product)))
             .collect(toList());
 }

 /**
  * 异步运算
  * @param product
  * @return
  */
 public List<String> findPricesFuture(String product) {
     List<CompletableFuture<String>> priceFutures = shopNames.stream()
             .map(shopName -> CompletableFuture.supplyAsync(
                     () -> String.format("%s 价格: %.2f", shopName, getPrice(product))))
             .collect(toList());
     //CompletableFuture 类中的 join 方法和 Future 接口中的 get 有相同的含义
     return priceFutures.stream()
             .map(CompletableFuture::join)
             .collect(toList());
 }

 private final Executor executor1 =
         //创建一个线程池,线程池中线程的数目为100和商店数目二者中较小的一个值
         Executors.newFixedThreadPool(Math.min(shopNames.size(), 100),
                 new ThreadFactory() {
                     public Thread newThread(Runnable r) {
                         Thread t = new Thread(r);
                         //使用守护线程——这种方式不会阻止程序的关停
                         t.setDaemon(true);
                         return t;
                     }
                 });

 /**
  * 异步运算:使用定制的执行器:调整线程池的大小
  * @param product
  * @return
  */
 public List<String> findPricesFuture1(String product) {
     List<CompletableFuture<String>> priceFutures = shopNames.stream()
             .map(shopName -> CompletableFuture.supplyAsync(() -> String.format("%s 价格: %.2f", shopName, getPrice(product)), executor1))
             .collect(toList());

     return priceFutures.stream()
             .map(CompletableFuture::join)
             .collect(toList());
 }

 @Test
 public void test3() {
     long start = System.nanoTime();
     System.out.println(findPrices("羽毛球"));
     System.out.println("Done in " + (System.nanoTime() - start) / 1_000_000 + " msecs");
     start = System.nanoTime();
     System.out.println(findPricesParallel("羽毛球"));
     System.out.println("Done in 并行:" + (System.nanoTime() - start) / 1_000_000 + " msecs");
     start = System.nanoTime();
     System.out.println(findPricesFuture("羽毛球"));
     System.out.println("Done in 并发:" + (System.nanoTime() - start) / 1_000_000 + " msecs");

     //并行和并发不相伯仲,究其原因都一样:它们内部采用的是同样的通用线程池,默认都使用固定数目的线程,具体线程数取决于
     // Runtime.getRuntime().availableProcessors() 的返回值。
     // 然而, CompletableFuture 具有一定的优势,因为它允许你对执行器( Executor )进行配置,尤其是线程池的大小
     start = System.nanoTime();
     System.out.println(findPricesFuture1("羽毛球"));
     System.out.println("Done in 并发(定制的执行器:调整线程池的大小):" + (System.nanoTime() - start) / 1_000_000 + " msecs");

 }

结果:

猜你喜欢

转载自blog.csdn.net/qq_39172525/article/details/86655590
今日推荐