【模擬情景
上一篇說到每一個shop都會提供一個價格查詢的服務,但是現在我們進行假設:
1. 所有的價格查詢是同步方式提供的
2. shop在返回價格的同時會返回一個折扣碼
3. 我們需要解析返回的字串,並且根據折扣碼區獲取折扣後的價格
4. 折扣後的價格計算依然是同步執行的
5. 查詢價格返回的字串格式為shopName:price:discountCode("沃爾瑪:200:15")
定義商店物件:Shop.java
public class Shop {
private String name;
public Shop(String name){
this.name = name;
}
public String getName(){
return name;
}
public String getPriceFormat(String product){
double price = calculatePrice(product);
//隨機返回一個折扣碼
Discount.Code code = Discount.Code.values()[random.nextInt(Discount.Code.values().length)];
return String.format("%s:%.2f:%s",name,price,code);
}
private double calculatePrice(String product){
delay();
return random.nextDouble() * product.charAt(0) + product.charAt(1);
}
private Random random = new Random();
/**
* 模擬耗時操作:延遲一秒
*/
private static void delay(){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
定義折扣物件:Discount.java
public class Discount {
public enum Code{
NONE(0),SILVER(5),GOLD(10),PLATINUM(15),DIAMOND(20);
private final int percantage;
Code(int percentage){
this.percantage = percentage;
}
}
public static String applyDiscount(Quote quote){
return quote.getShopName() + "prices is " + Discount.apply(quote.getPrice(),quote.getDiscountCode());
}
//計算折扣價格
private static Double apply(double price ,Code code){
//模擬遠端操作的延遲
delay();
return (price * (100 - code.percantage)) / 100;
}
private static void delay(){
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用於封裝解析getPriceFormat的字串物件:Quote.java
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;
}
public static Quote parse(String s){
String[] split = s.split(":");
String shopName = split[0];
Double price = Double.valueOf(split[1]);
Discount.Code code = Discount.Code.valueOf(split[2]);
return new Quote(shopName,price,code);
}
public double getPrice() {
return price;
}
public String getShopName() {
return shopName;
}
public Discount.Code getDiscountCode() {
return discountCode;
}
}
於是現在的任務就是:
1. 遠端查詢商品價格
2. 將獲得的字串解析成為Quote物件
3. 根據Quote物件遠端獲取折扣後的價格
現在先看看同步的方式來執行這個操作:
public List<String> findPrices2(String product){
return shops.stream()
.map(shop -> shop.getPriceFormat(product))
.map(Quote::parse)
.map(Discount::applyDiscount)
.collect(Collectors.toList());
}
因為有兩個耗時操作,每個1秒,耗時毫無疑問20秒以上:
【對多個非同步任務進行流水線操作
1. 獲取價格:使用CompletableFuture.supplyAsync()工廠方法即可,一旦執行結束每個CompletableFuture物件會包含一個shop返回的字串,這裡記住使用我們自定義的執行器。
2. 解析報價:一般情況下解析操作並不涉及到IO處理,所可以採用同步處理,所以這裡我們直接使用CompletableFuture物件的thenApply()方法,表明在的帶運算結果後立刻同步處理。
3. 計算折扣價格:這是一個遠端操作,肯定是需要非同步執行的,於是我們現在就有了兩次非同步處理(1.獲取價格,2.計算折扣)。現在使用級聯的方式將它們串聯起來工作。CompletableFuture提供了thenCompose方法,表明將兩個非同步操作進行流水線處理。第一個非同步操作的結果會成為第二個非同步操作的入參。使用這樣的方式,即使Future在向不同的shop手機報價,主執行緒依然可以執行其他操作,比如響應UI事件。
於是我們有了如下程式碼:
/**
* 非同步查詢
* 相比並行流的話CompletableFuture更有優勢:可以對執行器配置,設定執行緒池大小
*/
@SuppressWarnings("all")
private final Executor myExecutor =
Executors.newFixedThreadPool(Math.min(shops.size(), 100), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
public List<String> findPrices2Async(String product){
List<CompletableFuture<String>> futurePrices = shops.stream()
//首先非同步獲取價格
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getPriceFormat(product),myExecutor))
//將獲取的字串解析成物件
.map(future -> future.thenApply(Quote::parse))
//使用另一個非同步任務有獲取折扣價格
.map(future -> future.thenCompose(quote -> CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote),myExecutor)))
.collect(Collectors.toList());
//等待所有非同步任務完成
return futurePrices.stream().map(CompletableFuture::join).collect(Collectors.toList());
運算結果不到3秒:
【整合兩個CompletableFuture物件
我們剛才使用thenCompose()將兩個CompletableFuture結合了起來,並且一個CompletableFuture的運算結果將作為第二個CompletableFuture的入參。但是更多的情況是兩個不相干的CompletableFuture物件相互結合,並且我們也不希望第一個任務結束之後才開始第二個任務。這時可以使用thenCombine()。
比如我們獲取價格的同時也獲取匯率:
遠端獲取匯率方法:
/**
* 獲取匯率
*/
public double getRate(String type){
delay();
if("$".equals(type)){
return 0.3;
}
if("¥".equals(type)){
return 0.7;
}
return 1;
}
結合倆個非同步操作:
@Test
public void combine(){
Shop shop = new Shop("沃爾瑪");
Future<Double> futurePrice = CompletableFuture.supplyAsync(() -> shop.getPrice("iphoneX"))
.thenCombine(CompletableFuture.supplyAsync(() -> shop.getRate("$")),
(price,rate) -> price * rate);
}
thenCombine()接受兩個引數:
1. CompletableFuture物件:表明第二個非同步操作
2. BiFunction<? super T,? super U,? extends V>介面:兩個非同步操作的結果合併處理