6叁鉴、CompletableFuture組合式異步編程

github地址:https://github.com/WeidanLi/Java-jdk8-future

一土涝、Future接口

Future是jdk5的時(shí)候被引入的,目的是為了把耗時(shí)的操作解放出來幌墓,可以同時(shí)使用多核的優(yōu)勢進(jìn)行并行處理但壮。比如,我有一個(gè)頁面常侣,需要從多方獲取數(shù)據(jù)比如說從Twitter和Facebook獲取數(shù)據(jù)蜡饵,然后一起渲染的頁面上。這時(shí)候如果等待Twitter的數(shù)據(jù)獲取完在獲取FB的數(shù)據(jù)胳施,就顯得比較慢了溯祸,這時(shí)候可以通過Future來讓這兩個(gè)任務(wù)并行處理。

package cn.liweidan.myfuture;

import java.util.concurrent.*;

/**
 * <p>Desciption:Future執(zhí)行一個(gè)耗時(shí)的操作</p>
 * CreateTime : 2017/8/3 下午10:28
 * Author : Weidan
 * Version : V1.0
 */
public class Demo01 {

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<Double> future = executorService.submit(new Callable<Double>() {
            public Double call() throws Exception {
                Thread.sleep(1000L);
                return Math.random();
            }
        });
        Thread.sleep(1000L);
        try {
            /** 獲取線程執(zhí)行的結(jié)果,傳遞的是等待線程的時(shí)間焦辅,單位是1秒 */
            future.get(1, TimeUnit.SECONDS);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        System.out.println("共耗時(shí)" + (System.currentTimeMillis() - start) + "ms");// 1014ms
    }

}

(一)jdk8的CompletableFuture接口

1. Future的局限性

  • 等待Future集合所有都完成
  • 第二個(gè)線程依賴第一個(gè)線程的時(shí)候
  • 僅等待Future集合中最快完成的任務(wù)完成
  • 通過手工方式完成Future的運(yùn)行
  • 當(dāng)Future完成的時(shí)候通知下一個(gè)任務(wù)博杖,而不只是簡單的阻塞等待操作結(jié)果

2. 實(shí)現(xiàn)異步API

首先模擬在商店里面找到我們自己想要的產(chǎn)品的價(jià)格,在尋找的過程中讓線程休息1秒模擬網(wǎng)絡(luò)延遲筷登,然后隨機(jī)返回一個(gè)價(jià)格剃根。
第一步:
寫一個(gè)模擬延遲的方法delay(),模擬線程休息一秒鐘
第二步:
寫一個(gè)計(jì)算的方法前方,用于計(jì)算價(jià)格狈醉,這里我們使用隨機(jī)數(shù)生成
第三步:
暴露getPrice(String prodName)用于外部調(diào)用。
第四步:
暴露異步獲取線程的接口
Shop.java:

package cn.liweidan.myfuture.shop;

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

/**
 * <p>Desciption:商店類</p>
 * CreateTime : 2017/8/3 下午11:16
 * Author : Weidan
 * Version : V1.0
 */
public class Shop {

    private String shopName;

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

    public Shop() {
    }

    /**
     * 獲取產(chǎn)品價(jià)格
     * @param prodName 產(chǎn)品名
     * @return
     */
    public double getPrice(String prodName){
        return caculatePrice(prodName);
    }

    /**
     * 模擬網(wǎng)絡(luò)延遲
     */
    private static void delay(){
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 計(jì)算產(chǎn)品價(jià)格
     * @param prodName
     * @return
     */
    private double caculatePrice(String prodName){
        delay();
        return new Random().nextDouble() * prodName.charAt(0) + prodName.charAt(1);
    }

    /**
     * 異步請求產(chǎn)品價(jià)格
     * @param prodName
     * @return
     */
    public Future<Double> getPriceAsync(String prodName){
        /** 1. 使用第一種方式 */
        // CompletableFuture<Double> future = new CompletableFuture<>();
        // new Thread(() -> {
        //     try {
        //         double price = caculatePrice(prodName);
        //         future.complete(price);
        //     } catch (Exception e) {
        //         /** 出現(xiàn)異常 */
        //         future.completeExceptionally(e);
        //     }
        // }).start();
        // return future;

        return CompletableFuture.supplyAsync(() -> caculatePrice(prodName));// 2. 第二種方式惠险,工廠模式的方式
    }
}

接下來就來運(yùn)行了苗傅,代碼中模擬獲取商店價(jià)格的同時(shí)還需要執(zhí)行其他任務(wù),如果串行執(zhí)行就是需要2s班巩,但是其實(shí)使用線程去獲取的話渣慕,只需要1s,也就是兩個(gè)耗時(shí)的任務(wù)是一起完成的抱慌。

package cn.liweidan.myfuture.shop;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * <p>Desciption:獲取多家商店產(chǎn)品價(jià)格Demo</p>
 * CreateTime : 2017/8/3 下午11:15
 * Author : Weidan
 * Version : V1.0
 */
public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        /** 有個(gè)商店 */
        Shop shop = new Shop("BestShop");
        long startTime = System.nanoTime();
        /** 獲取運(yùn)行的線程 */
        Future<Double> shopPriceAsync = shop.getPriceAsync("best product");
        long invocationTime = ((System.nanoTime() - startTime) / 1_000_000);
        System.out.println("Invocation returned after" + invocationTime + "ms");
        
        /** 模擬做其他事情的耗時(shí) */
        Thread.sleep(1000L);

        try {
            /** 獲取結(jié)果 */
            double aDouble = shopPriceAsync.get(1, TimeUnit.SECONDS);
            System.out.println("Price is " + aDouble);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        long retrievalTime = (System.nanoTime() - startTime) / 1_000_000;
        System.out.println("Price returned after " + retrievalTime + "ms");// 1025ms
    }
}

3. 實(shí)現(xiàn)異步API

現(xiàn)在有個(gè)需求是:對多個(gè)商店的產(chǎn)品進(jìn)行查詢摇庙,獲取最佳價(jià)格的產(chǎn)品

第一種方式:順序流查詢(Done in 4131ms)

package cn.liweidan.myfuture.shop;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>Desciption:對多個(gè)商店進(jìn)行查詢</p>
 * CreateTime : 2017/8/5 下午2:47
 * Author : Weidan
 * Version : V1.0
 */
public class Demo03 {

    /** 多家商店 */
    List<Shop> shops;

    public static void main(String[] args) {
        Demo03 demo03 = new Demo03();
        demo03.shops = Arrays.asList(new Shop("Shop1"),
                new Shop("Shop2"),
                new Shop("Shop3"),
                new Shop("Shop4"));
        long start = System.currentTimeMillis();
        List<String> iPhone7 = demo03.findPrices("iPhone7");
        System.out.println("Done in " + (System.currentTimeMillis() - start) + "ms");// Done in 4131ms
    }

    public List<String> findPrices(String prodName){
        return shops.stream()
                .map(shop -> String.format("%s price is %.2f", shop.getShopName(), shop.getPrice(prodName)))
                .collect(Collectors.toList());

    }
}

第二種方式:并行流查詢所有的商店遥缕。(Done in 1162ms)

package cn.liweidan.myfuture.shop;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>Desciption:對多個(gè)商店進(jìn)行查詢</p>
 * CreateTime : 2017/8/5 下午2:47
 * Author : Weidan
 * Version : V1.0
 */
public class Demo03 {

    /** 多家商店 */
    List<Shop> shops;

    public static void main(String[] args) {
        Demo03 demo03 = new Demo03();
        demo03.shops = Arrays.asList(new Shop("Shop1"),
                new Shop("Shop2"),
                new Shop("Shop3"),
                new Shop("Shop4"));
        long start = System.currentTimeMillis();
        List<String> iPhone7 = demo03.findPrices("iPhone7");
        System.out.println("Done in " + (System.currentTimeMillis() - start) + "ms");// stream : Done in 4131ms
    }

    public List<String> findPrices(String prodName){
        /*return shops.stream()
                .map(shop -> String.format("%s price is %.2f", shop.getShopName(), shop.getPrice(prodName)))
                .collect(Collectors.toList());*/
        return shops.parallelStream()
                .map(shop -> String.format("%s price is %.2f", shop.getShopName(), shop.getPrice(prodName)))
                .collect(Collectors.toList());
    }
}

第三種方式:使用CompletableFuture發(fā)起異步請求(Done in 2127ms)

package cn.liweidan.myfuture.shop;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * <p>Desciption:對多個(gè)商店進(jìn)行查詢</p>
 * CreateTime : 2017/8/5 下午2:47
 * Author : Weidan
 * Version : V1.0
 */
public class Demo03 {

    /** 多家商店 */
    List<Shop> shops;

    public static void main(String[] args) {
        Demo03 demo03 = new Demo03();
        demo03.shops = Arrays.asList(new Shop("Shop1"),
                new Shop("Shop2"),
                new Shop("Shop3"),
                new Shop("Shop4"));
        long start = System.currentTimeMillis();
        List<String> iPhone7 = demo03.findPrices("iPhone7");
        System.out.println("Done in " + (System.currentTimeMillis() - start) + "ms");// stream : Done in 4131ms
    }

    public List<String> findPrices(String prodName){
        /** 順序流查詢 */
        /*return shops.stream()
                .map(shop -> String.format("%s price is %.2f", shop.getShopName(), shop.getPrice(prodName)))
                .collect(Collectors.toList());*/

        /** 并行流查詢 */
        /*return shops.parallelStream()
                .map(shop -> String.format("%s price is %.2f", shop.getShopName(), shop.getPrice(prodName)))
                .collect(Collectors.toList());*/

        /** CompletableFuture方式異步請求 */
        List<CompletableFuture<String>> completableFutureList = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s price is %.2f", shop.getShopName(), shop.getPrice(prodName))))
                .collect(Collectors.toList());

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

結(jié)果并不像第二種方式中的那樣如意,這時(shí)候我們可以引出下一個(gè)話題宵呛,對CompletableFuture的優(yōu)化单匣,CompletableFuture和并行流的對比,優(yōu)勢就在于CompletableFuture允許我們對執(zhí)行器進(jìn)行優(yōu)化宝穗。

4. 定制Excutor執(zhí)行器

在CompletableFuture.supplyAsync這個(gè)工廠方法中户秤,我們可以傳遞我們自定義的Executor給線程。執(zhí)行器的定義就需要我們斟酌了逮矛,在《Java并發(fā)編程實(shí)戰(zhàn)》中有一道公式:N(threads)=Ncpu * Ucpu * (1+W/C)
其中鸡号,Ncpu是cpu的個(gè)數(shù),Ucpu是我們期望的CPU的利用率须鼎,而W/C是等待時(shí)間與計(jì)算時(shí)間的比率鲸伴。我們就可以根據(jù)這個(gè)公式計(jì)算出我們所需要的線程數(shù)量了,但是線程數(shù)量不宜過多晋控,也不宜過少

/**
 * 獲取一個(gè)優(yōu)化的執(zhí)行器
 * @return
 */
private final Executor getExecutor(){
    /** 在設(shè)置線程數(shù)的時(shí)候汞窗,取一個(gè)上限的值,即如果商城超過100個(gè)的時(shí)候我們永遠(yuǎn)都只要100個(gè)線程數(shù) */
    return Executors.newFixedThreadPool(Math.min(this.shops.size(), 100), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);// 設(shè)置守護(hù)線程赡译,Java無法停止正在運(yùn)行的線程
            return t;
        }
    });
}

public List<String> findPrices(String prodName){
    /** CompletableFuture方式異步請求 */
    List<CompletableFuture<String>> completableFutureList = shops.stream()
            .map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s price is %.2f", shop.getShopName(), shop.getPrice(prodName)), 
                  getExecutor()))// 將我們自己定制的Executor傳遞給線程
            .collect(Collectors.toList());

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

二仲吏、對多個(gè)異步任務(wù)進(jìn)行流水線操作

我們假設(shè)現(xiàn)在需要實(shí)現(xiàn)一個(gè)需求,就是所有商店都允許使用一個(gè)折扣,然后我們異步獲取所有商店的價(jià)格后裹唆,再通知另外一個(gè)異步線程誓斥,計(jì)算出價(jià)格打折扣以后的數(shù)據(jù)。這時(shí)候求出折扣的價(jià)格線程就依賴到了商店獲取價(jià)格的線程了许帐。

假設(shè)需要修改商店價(jià)格和折扣返回的形式是:BestShop:123.26:GOLD劳坑。這時(shí)候我們需要對getPrice進(jìn)行修改:
Shop.java:

package cn.liweidan.myfuture.streamm;

import java.util.Random;

/**
 * <p>Desciption:參與折扣商店類</p>
 * CreateTime : 2017/8/5 下午4:22
 * Author : Weidan
 * Version : V1.1
 */
public class Shop {


    private String shopName;

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

    public Shop() {
    }

    /**
     * 獲取產(chǎn)品價(jià)格
     * @param prodName 產(chǎn)品名
     * @return
     */
    public String getPrice(String prodName){
        double price = caculatePrice(prodName);
        Discount.Code code = Discount.Code.values()[new Random().nextInt(Discount.Code.values().length)];
        return String.format("%s:%.2f:%s", shopName, price, code);
    }

    /**
     * 模擬網(wǎng)絡(luò)延遲
     */
    private static void delay(){
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 計(jì)算產(chǎn)品價(jià)格
     * @param prodName
     * @return
     */
    private double caculatePrice(String prodName){
        delay();
        return new Random().nextDouble() * prodName.charAt(0) + prodName.charAt(1);
    }

    public String getShopName() {
        return shopName;
    }

    public void setShopName(String shopName) {
        this.shopName = shopName;
    }

}

這時(shí)候我們需要定義折扣的枚舉以及折扣的計(jì)算類。
Discount.java:

package cn.liweidan.myfuture.streamm;

import static com.sun.tools.javac.util.Constants.format;

/**
 * <p>Desciption:折扣類舞吭,包含折扣方式枚舉</p>
 * CreateTime : 2017/8/5 下午4:19
 * Author : Weidan
 * Version : V1.0
 */
public class Discount {

    /**
     * 返回計(jì)算折扣后的價(jià)格
     * @param price
     * @param discountCode
     * @return
     */
    public static String apply(double price, Code discountCode) {
        delay();// 模擬請求折扣的延遲
        return format(price * (100 - discountCode.per) / 100);
    }

    private static void delay() {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 應(yīng)用一個(gè)Quote計(jì)算的類
     * @param quote
     * @return
     */
    public static String applyDiscount(Quote quote){
        return quote.getShopName() + " price is " +
                Discount.apply(quote.getPrice(), quote.getDiscountCode());
    }

    public enum Code {
        NONE(0), SILVER(5), GOLD(10), PLATINUM(15), DIAMOND(20);

        /** 折扣百分率 */
        private final int per;

        Code(int per){
            this.per = per;
        }

    }

}

Quote.java:

package cn.liweidan.myfuture.streamm;

/**
 * <p>Desciption:實(shí)現(xiàn)折扣服務(wù)</p>
 * CreateTime : 2017/8/5 下午4:26
 * Author : Weidan
 * Version : V1.0
 */
public 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;
    }

    /**
     * 解析商店返回的字符串泡垃,
     * @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 code = Discount.Code.valueOf(split[2]);
        return new Quote(shopName, price, code);
    }


    public String getShopName() {
        return shopName;
    }

    public double getPrice() {
        return price;
    }

    public Discount.Code getDiscountCode() {
        return discountCode;
    }
}

這時(shí)候開始我們需要編寫,兩個(gè)依賴的異步線程的方法:

package cn.liweidan.myfuture.streamm;


import java.util.Arrays;
import java.util.List;
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;

/**
 * <p>Desciption:對多個(gè)異步任務(wù)進(jìn)行流水線操作</p>
 * CreateTime : 2017/8/5 下午4:44
 * Author : Weidan
 * Version : V1.0
 */
public class Demo05 {

    List<Shop> shops;

    public static void main(String[] args) {
        Demo05 demo05 = new Demo05();
        demo05.shops = Arrays.asList(new Shop("Shop1"),
                new Shop("Shop2"),
                new Shop("Shop3"),
                new Shop("Shop4"));
        long start = System.currentTimeMillis();
        List<String> iPhone7 = demo05.findPrices("iPhone7");
        System.out.println("Done in " + (System.currentTimeMillis() - start) + "ms");// stream : Done in 4131ms
    }

    /**
     * 尋找打折扣后產(chǎn)品價(jià)格
     * @param prodName
     * @return
     */
    public List<String> findPrices(String prodName){
        List<CompletableFuture<String>> completableFutures = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(prodName), getExecutor()))// 以異步的形式取得shop原始價(jià)格
                .map(future -> future.thenApply(Quote::parse))// Quote對象存在的時(shí)候羡鸥,對其進(jìn)行轉(zhuǎn)換
                .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), getExecutor())))// 使用另一個(gè)異步任務(wù)構(gòu)造申請折扣
                .collect(Collectors.toList());

        return completableFutures.stream()
                .map(CompletableFuture::join)// 等待流中所有Future完畢并提取各自返回值
                .collect(Collectors.toList());
    }

重點(diǎn)放在findPrices(String prodName)這個(gè)方法上:

  1. 我們拿到所有的shop進(jìn)行遍歷蔑穴,把shop流變成一個(gè)CompletableFuture<String>的流,這個(gè)六包含著所有商店獲取的價(jià)格字符串(BestShop:123.26:GOLD)格式的線程惧浴。
  2. 拿到字符串的時(shí)候存和,我們通過future.thenApply方法將這些字符串轉(zhuǎn)換成quote對象(該對象包含字符串解析出來的信息)
  3. future.thenCompose再把上一步拿到的已經(jīng)轉(zhuǎn)換成Quote對象的CompletableFuture流進(jìn)行進(jìn)一步的通知,即通知CPU可以異步執(zhí)行計(jì)算折扣的代碼了
  4. 收集到所有的CompletableFuture流
  5. CompletableFuture::join意為等待所有的Future線程(獲取價(jià)格衷旅、計(jì)算折扣)執(zhí)行完成的時(shí)候捐腿,收集到所有的數(shù)據(jù)進(jìn)行返回。

合并兩個(gè)完全不相干的任務(wù)

上一節(jié)柿顶,講了thenCompose茄袖,接下來有另外一個(gè)方法,即thenCompine的使用嘁锯。這個(gè)方法和thenCompose的區(qū)別即:我們需要把兩個(gè)不互相依賴的結(jié)果同時(shí)計(jì)算出來進(jìn)行計(jì)算宪祥,也就是我不希望第一個(gè)任務(wù)完成以后再來開始第二個(gè)任務(wù)的時(shí)候就可以使用這個(gè)方法。

那么現(xiàn)在有個(gè)需求家乘,就是我在計(jì)算出來商店的價(jià)格的時(shí)候蝗羊,同時(shí)查找當(dāng)時(shí)的匯率是多少,然后在最后使用這兩個(gè)數(shù)字進(jìn)行相乘仁锯。

package cn.liweidan.myfuture.streamm;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * <p>Desciption:thenCompine的使用</p>
 * CreateTime : 2017/8/8 下午2:19
 * Author : Weidan
 * Version : V1.0
 */
public class Demo06 {

    /**
     * 模擬查詢價(jià)格
     * @return
     */
    public static Double getPrice(){
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
        }
        return Math.random() * 10;
    }

    /**
     * 模擬查詢匯率
     * @return
     */
    public static Double getRate(){
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
        }
        return Math.random();
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> getPrice())// 第一個(gè)任務(wù)并獲取返回值
                .thenCombine(CompletableFuture.supplyAsync(() -> getRate()), (price, rate) -> price * rate);// 第二個(gè)任務(wù)耀找,獲取返回值以后與第一個(gè)返回值進(jìn)行計(jì)算
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("done in " + (System.currentTimeMillis() - start) + "ms");// done in 1118ms
    }
}

Completion事件

一組CompletableFuture完成以后執(zhí)行的任務(wù)即使Completion事件,當(dāng)對所有商店進(jìn)行查詢的時(shí)候业崖,可能有些快一點(diǎn)野芒,有些慢一點(diǎn),但是我們會(huì)去等待比較慢的任務(wù)處理完成再來進(jìn)行接下來的邏輯双炕,這時(shí)候就可以使用future.thenAccept方法來完成這個(gè)任務(wù)复罐。

  1. 修改shop獲取價(jià)格的延遲是隨機(jī)性的。
/**
 * 模擬網(wǎng)絡(luò)延遲
 */
private static void delay(){
    int delay = new Random().nextInt(2000) + 500;
    try {
        Thread.sleep(delay);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}
  1. 獲取查詢價(jià)格時(shí)候的線程流
/**
 * 獲取所有商店查詢的線程流
 * @param prodName
 * @return
 */
public Stream<CompletableFuture<String>> findPricesStream(String prodName) {
    return shops.stream()
            .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(prodName), getExecutor()))// 以異步的形式取得shop原始價(jià)格
            .map(future -> future.thenApply(Quote::parse))// Quote對象存在的時(shí)候雄家,對其進(jìn)行轉(zhuǎn)換
            .map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote), getExecutor())));
}
  1. 這時(shí)候我們需要對線程流中的每個(gè)線程執(zhí)行thenAccept方法效诅,并且打印所有的價(jià)格胀滚,順便打印所有完成的時(shí)間。
public static void main(String[] args) {
    long start = System.currentTimeMillis();
    Demo06 demo06 = new Demo06();
    demo06.shops = Arrays.asList(new Shop("Shop1"),
            new Shop("Shop2"),
            new Shop("Shop3"),
            new Shop("Shop4"));
    Stream<CompletableFuture<Void>> myIphoneThread  // 均全部返回CompletableFuture<Void>
            = demo06.findPricesStream("myIphone").map(f -> f.thenAccept(priceStr -> {
        System.out.println(priceStr + "(done in " + (System.currentTimeMillis() - start) + " ms)");// 對每個(gè)價(jià)格進(jìn)行輸出打印
    }));

    /** 獲取數(shù)組 */
    CompletableFuture[] completableFutures = myIphoneThread.toArray(size -> new CompletableFuture[size]);

    /** 等待所有線程完成 */
    CompletableFuture.allOf(completableFutures).join();

    System.out.println("all done in " + (System.currentTimeMillis() - start) + "ms");

    /*
    Shop1 price is 112.419(done in 2792 ms)
    Shop4 price is 117.3345(done in 2990 ms)
    Shop2 price is 179.71(done in 3409 ms)
    Shop3 price is 131.82299999999998(done in 3584 ms)
    all done in 3584ms
     */
}

可以發(fā)現(xiàn)乱投,最長的時(shí)間即使整個(gè)程序運(yùn)行的時(shí)間咽笼,通過allOf方法,等待所有線程執(zhí)行完畢以后打印出總的時(shí)間戚炫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剑刑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子双肤,更是在濱河造成了極大的恐慌施掏,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茅糜,死亡現(xiàn)場離奇詭異七芭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蔑赘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門狸驳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缩赛,你說我怎么就攤上這事耙箍。” “怎么了酥馍?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵辩昆,是天一觀的道長。 經(jīng)常有香客問我旨袒,道長卤材,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任峦失,我火速辦了婚禮,結(jié)果婚禮上术吗,老公的妹妹穿的比我還像新娘尉辑。我一直安慰自己,他們只是感情好较屿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布隧魄。 她就那樣靜靜地躺著,像睡著了一般隘蝎。 火紅的嫁衣襯著肌膚如雪购啄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天嘱么,我揣著相機(jī)與錄音狮含,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛几迄,可吹牛的內(nèi)容都是我干的蔚龙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼映胁,長吁一口氣:“原來是場噩夢啊……” “哼木羹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起解孙,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤坑填,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后弛姜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脐瑰,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年娱据,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚪黑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡中剩,死狀恐怖忌穿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情结啼,我是刑警寧澤掠剑,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站郊愧,受9級(jí)特大地震影響朴译,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜属铁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一眠寿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧焦蘑,春花似錦盯拱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽反粥。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間法焰,已是汗流浹背贱鼻。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工窍侧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壤蚜。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像详羡,于是被迫代替她去往敵國和親仍律。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 在現(xiàn)代軟件開發(fā)中实柠,系統(tǒng)功能越來越復(fù)雜水泉,管理復(fù)雜度的方法就是分而治之,系統(tǒng)的很多功能可能會(huì)被切分為小的服務(wù)窒盐,對外提供...
    天堂鳥6閱讀 7,149評論 0 23
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理草则,服務(wù)發(fā)現(xiàn),斷路器蟹漓,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,815評論 1 19
  • 從哪說起呢炕横? 單純講多線程編程真的不知道從哪下嘴。葡粒。 不如我直接引用一個(gè)最簡單的問題份殿,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,757評論 1 17
  • 在愛情里卿嘲,女人往往愛一個(gè)人愛到?jīng)]了自己。為了愛情夫壁,她愿意冠以父姓拾枣,愿意三從四德,愿意生兒育女盒让。為了愛情梅肤,她可以拋開...
    子諾A閱讀 720評論 0 0