/** * @Auther: cheng.tang * @Date: 2019/3/2 * @Description: */ package com.tangcheng.learning.concurrent; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import java.util.concurrent.*; import java.util.stream.Stream; /** * @Auther: cheng.tang * @Date: 2019/3/2 * @Description: */ @Slf4j public class CompletableFutureTest { /** * 16:12:01.109 [main] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - start * 16:12:01.168 [main] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - 小於coreSize時,每次新建執行緒,都會列印這個日誌。Runtime.getRuntime().availableProcessors():8 * 16:12:01.171 [main] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - 小於coreSize時,每次新建執行緒,都會列印這個日誌。Runtime.getRuntime().availableProcessors():8 * 16:12:01.172 [main] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - 小於coreSize時,每次新建執行緒,都會列印這個日誌。Runtime.getRuntime().availableProcessors():8 * 16:12:03.172 [Thread-2] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - finish :3 * 16:12:04.172 [Thread-0] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - finish :1 * 16:12:05.172 [Thread-1] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - finish :2 * 16:12:05.172 [main] INFO com.tangcheng.learning.concurrent.CompletableFutureTest - last log */ @Test public void testCompletableFuture() { log.info("start"); /** * runSync不要求有返回值 */ CompletableFuture[] completableFutures = Stream.of(1, 2, 3).map(item -> CompletableFuture.supplyAsync(() -> { try { int timeout = ThreadLocalRandom.current().nextInt(1, 5); if (timeout == 1) { throw new IllegalArgumentException("出錯了,為什麼是1"); } TimeUnit.SECONDS.sleep(timeout); } catch (InterruptedException e) { e.printStackTrace(); } log.info("finish :{}", item); return "finish" + item; }, executor).exceptionally(ex -> { log.error("exceptionally 一個任務失敗了 item:{},{}", item, ex.getMessage()); return "一個任務失敗了" + ex.getMessage(); } )).toArray(CompletableFuture[]::new); //allOf():工廠方法接受由CompletableFuture物件構成的陣列,陣列中所有的CompletableFuture完成後它返回一個CompletableFuture<Void>物件。 CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(completableFutures); //等待所有的子執行緒執行完畢 voidCompletableFuture.join(); log.info("last log "); } private static final Executor executor = Executors.newFixedThreadPool(Math.min(Runtime.getRuntime().availableProcessors(), 100), new ThreadFactory() { @Override public Thread newThread(Runnable r) { log.info("小於coreSize時,每次新建執行緒,都會列印這個日誌。Runtime.getRuntime().availableProcessors():{}", Runtime.getRuntime().availableProcessors()); Thread t = new Thread(r); // 使用守護執行緒---這種方式不會阻止程式關掉 t.setDaemon(true); return t; } }); }
Future介面的侷限性
Future提供了isDone()方法檢測非同步計算是否已經結束,get()方法等待非同步操作結束,以及獲取計算的結果。但是這些操作依然很難實現:等到所有Future任務完成,通知執行緒獲取結果併合並。下面就一起來看看JDK8中新引入的CompletableFuture。
使用CompletableFuture構建非同步應用
CompletableFuture實現了Future介面,它的complete()方法就相當於結束CompletableFuture物件的執行,並設定變數的值。
/**
* @author yangfan
* @date 2017/04/11
*/
public class Shop {
private static final Random random = new Random();
private String name;
public Shop(String name) {
this.name = name;
}
public String getPrice(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);
}
public Future<Double> getPriceAsync1(String product) {
// 建立CompletableFuture物件,它會包含計算的結果
CompletableFuture<Double> futurePrice = new CompletableFuture<>();
// 在另一個執行緒中以非同步方式執行計算
new Thread(() -> {
try {
double price = calculatePrice(product);
// 需長時間計算的任務結束並得出結果時,設定Future的返回值
futurePrice.complete(price);
} catch (Exception e) {
//異常處理
futurePrice.completeExceptionally(e);
}
}).start();
// 無需等待還沒結束的計算,直接返回Future物件
return futurePrice;
}
public Future<Double> getPriceAsync(String product) {
return CompletableFuture.supplyAsync(() -> calculatePrice(product));
}
private double calculatePrice(String product) {
randomDelay();
return random.nextDouble() * product.charAt(0) + product.charAt(1);
}
public static void delay() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
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("BestShop");
long start = System.nanoTime();
// 查詢商店,試圖去的商品的價格
Future<Double> futurePrice = shop.getPriceAsync("my favorte product");
long invocationTime = (System.nanoTime() - start) / 1_000_000;
System.out.println("Invocation returned after " + invocationTime + " msecs");
// 執行更多工,比如查詢其他商店
//doSomethingElse();
// 在計算商品價格的同時
try {
// 從Future物件中讀取價格,如果價格未知,會發生阻塞
double price = futurePrice.get();
System.out.printf("Price is %.2f%n", price);
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
System.out.println("Price returned after " + retrievalTime + " msecs");
}
public String getName() {
return name;
}
}
上面的例子來自於《Java8實戰》,模擬了一個耗時的操作,然後通過CompletableFuture包裝成非同步方法進行呼叫。注意程式碼裡演示了兩種方式,一種自己new一個執行緒再呼叫complete方法,還有就是用CompletableFuture自身提供的工廠方法,CompletableFuture.supplyAsync,它能更容易地完成整個流程,還不用擔心實現的細節。
現在看來好像和Future方式也沒有什麼區別,都是包裝一下最後通過get()方法獲取結果,但是CompletableFuture配合Java8用起來就非常厲害了,它提供了很多方便的方法,下面進行一個演示。
同樣是價格查詢,我們現在接到一個需求,就是獲取一個商品在不同商店的報價,一般來說用傳統的方式就是寫一個for
迴圈,遍歷商店然後獲取價格,要想效率快一點我們也可以用並行流的方式來查詢,但是並行流返回的結果是無序的。下面再將非同步也引入,我們可以實現有序的並行操作:
private static List<String> findPrices_1(String product) {
List<CompletableFuture<String>> priceFutures = shops.stream()
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is " + shop.getPrice(product), executor)).collect(Collectors.toList());
return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
這裡建立CompletableFuture和呼叫join()方法是兩個不同的流是有原因的,如果只在一個流裡,那麼就沒有非同步的效果了,下一個Future必須等到上一個完成後才會被建立和執行。上面的程式碼執行效率並不會比並行流的效率差。
預設情況下,並行流和CompletableFuture預設都是用固定數目的執行緒,都是取決於Runtime. getRuntime().availableProcessors()的返回值。並行流的執行緒池並不好控制,其本質是內部隱含使用了ForkJoinPool執行緒池,最大併發數可以通過系統變數設定。所以CompletableFuture也就具有了優勢,它允許配置自定義的執行緒池,這也可以為實際應用程式帶來效能上的提升(並行流無法提供的API),CompletableFuture.supplyAsync(Supplier supplier,Executor executor)提供了過載的方法來指定執行器使用自定義的執行緒池。
// 建立一個執行緒池,執行緒池中執行緒的數目為100和商店數目二者中較小的一個值
private static final Executor executor = 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;
}
});
並行–使用流還是CompletableFutures?
現在我們知道對集合進行平行計算有兩種方式:
- 轉化為並行流,利用map開展工作。
- 取出每一個元素,建立執行緒,在CompletableFuture內對其進行操作
後者提供了更多的靈活性,你可以調整執行緒池的大小,而這能幫助我們確保整體的計算不會因為執行緒都在等待I/O而發生阻塞。
那麼如何選擇呢,建議如下:
- 進行計算密集型的操作,並且沒有I/O,那麼推薦使用Stream介面,因為實現簡單,同時效率也可能是最高的(如果所有的執行緒都是計算密集型的,那就沒有必要建立比處理器核數更多的執行緒)。
- 如果並行操作設計等到I/O的操作(網路連線,請求等),那麼使用CompletableFuture靈活性更好,通過控制執行緒數量來優化程式的執行。
CompletableFuture還提供了了一些非常有用的操作例如,thenApply(),thenCompose(),thenCombine()等
- thenApply()是操作完成後將結果傳入進行轉換
- thenCompose()是對兩個非同步操作進行串聯,第一個操作完成時,對第一個CompletableFuture物件呼叫thenCompose,並向其傳遞一個函式。當第一個CompletableFuture執行完畢後,它的結果將作為該函式的引數,這個函式的返回值是以第一個CompletableFuture的返回做輸入計算出第二個CompletableFuture物件。
- thenCombine()會非同步執行兩個CompletableFuture任務,然後等待它們計算出結果後再進行計算。
private static List<String> findPrices(String product) {
List<CompletableFuture<String>> priceFutures = 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());
return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
通常而言,名稱中不帶Async的方法和它的前一個任務一樣,在同一個執行緒中執行;而名稱以Async結尾的方法會將後續的任務提交到一個執行緒池,所以每個任務都是由不同執行緒處理的,例如thenApplyAsync(),thenComposeAsync()等。
最後看一段利用thenAccept()來使用非同步計算結果的程式碼:
// 這裡演示獲取最先返回的資料
public static 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)));
}
public static void main(String[] args) {
long start = System.nanoTime();
// System.out.println(findPrices("myPhone27S"));
CompletableFuture[] futures = findPricesStream("myPhone27S")
.map(f -> f.thenAccept(s -> System.out.println(s + " (done in " +
((System.nanoTime() - start) / 1_000_000) + " msecs)")))
.toArray(CompletableFuture[]::new);
// CompletableFuture.allOf(futures).join();
CompletableFuture.anyOf(futures).join();
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("Done in " + duration + " msecs");
}
這樣就幾乎無需等待findPricesStream的呼叫,實現了一個真正的非同步方法。
https://blog.csdn.net/qq_32331073/article/details/81503475
2.執行一個簡單的非同步stage
下面的例子解釋瞭如何建立一個非同步執行Runnable
的stage。
static void runAsyncExample() {
CompletableFuture cf = CompletableFuture.runAsync(() -> {
assertTrue(Thread.currentThread().isDaemon());
randomSleep();
});
assertFalse(cf.isDone());
sleepEnough();
assertTrue(cf.isDone());
}
這個例子想要說明兩個事情:
CompletableFuture
中以Async
為結尾的方法將會非同步執行- 預設情況下(即指沒有傳入
Executor
的情況下),非同步執行會使用ForkJoinPool
實現,該執行緒池使用一個後臺執行緒來執行Runnable
任務。注意這只是特定於CompletableFuture
實現,其它的CompletableStage
實現可以重寫該預設行為。
https://segmentfault.com/a/1190000013452165?utm_source=index-hottest
聯合多個CompletableFuture
另外一組方法允許將多個CF
聯合在一起。我們已經看見過靜態方法allOf
,當其所有的元件均完成時,它就會處於完成狀態,與之對應的方法也就是anyOf
,返回值同樣是void,當其任意一個元件完成時,它就會完成。
除了這兩個方法以外,這個組中其他的方法都是例項方法,它們能夠將receiver按照某種方式與另外一個CF
聯合在一起,然後將結果傳遞到給定的函式中。
https://www.cnblogs.com/kexianting/p/8692437.html
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; import java.util.stream.Stream; /** * 最佳價格查詢器 */ public class BestFinder { List<Shop> shops = Arrays.asList( new Shop("A"), new Shop("B"), new Shop("C"), new Shop("D"), new Shop("E"), new Shop("F"), new Shop("G"), new Shop("H"), new Shop("I"), new Shop("J") ); public void findPricesContinue(String product){ long st = System.currentTimeMillis(); Stream<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))); //thenAccept()會在CompletableFuture完成之後使用他的返回值,這裡會持續執行子執行緒 CompletableFuture[] futures = futurePrices.map(f -> f.thenAccept(s -> { String sout = String.format("%s done in %s mesc",s,(System.currentTimeMillis() - st)); System.out.println(sout); })) .toArray(size -> new CompletableFuture[size]); //allOf()工廠方法接受由CompletableFuture物件構成的陣列,這裡使用其等待所有的子執行緒執行完畢 CompletableFuture.allOf(futures).join(); } /** * 非同步查詢 * 相比並行流的話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; } });
https://segmentfault.com/a/1190000012859835
https://segmentfault.com/a/1190000013452165?utm_source=index-hottest