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è)方法上:
- 我們拿到所有的shop進(jìn)行遍歷蔑穴,把shop流變成一個(gè)CompletableFuture<String>的流,這個(gè)六包含著所有商店獲取的價(jià)格字符串(BestShop:123.26:GOLD)格式的線程惧浴。
- 拿到字符串的時(shí)候存和,我們通過
future.thenApply
方法將這些字符串轉(zhuǎn)換成quote對象(該對象包含字符串解析出來的信息) - future.thenCompose再把上一步拿到的已經(jīng)轉(zhuǎn)換成Quote對象的CompletableFuture流進(jìn)行進(jìn)一步的通知,即通知CPU可以異步執(zhí)行計(jì)算折扣的代碼了
- 收集到所有的CompletableFuture流
- 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ù)复罐。
- 修改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);
}
}
- 獲取查詢價(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())));
}
- 這時(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í)間戚炫。