提升應用性能的時候很容易就會想起異步,異步去處理一些任務這樣主線程能夠儘快響應。
經過閱讀本篇文章你將瞭解到:java
查詢全部商店某個商品的價格並返回,而且查詢商店某個商品的價格的API爲同步
一個Shop類,提供一個名爲getPrice的同步方法mysql
public class Shop { private Random random = new Random(); /** * 根據產品名查找價格 * */ public double getPrice(String product) { return calculatePrice(product); } /** * 計算價格 * * @param product * @return * */ private double calculatePrice(String product) { delay(); //random.nextDouble()隨機返回折扣 return random.nextDouble() * product.charAt(0) + product.charAt(1); } /** * 經過睡眠模擬其餘耗時操做 * */ private void delay() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
查詢商品的價格爲同步方法,並經過sleep方法模擬其餘操做。這個場景模擬了當須要調用第三方API,但第三方提供的是同步API,在沒法修改第三方API時如何設計代碼調用提升應用的性能和吞吐量,這時候可使用CompletableFuture類git
Completable是Future接口的實現類,在JDK1.8中引入github
CompletableFuture的建立:sql
使用new方法數據庫
CompletableFuture<Double> futurePrice = new CompletableFuture<>();
使用CompletableFuture#completedFuture靜態方法建立設計模式
public static <U> CompletableFuture<U> completedFuture(U value) { return new CompletableFuture<U>((value == null) ? NIL : value); }
參數的值爲任務執行完的結果,通常該方法在實際應用中較少應用緩存
使用 CompletableFuture#supplyAsync靜態方法建立
supplyAsync有兩個重載方法:併發
//方法一 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) { return asyncSupplyStage(asyncPool, supplier); } //方法二 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) { return asyncSupplyStage(screenExecutor(executor), supplier); }
runAsync有兩個重載方法框架
//方法一 public static CompletableFuture<Void> runAsync(Runnable runnable) { return asyncRunStage(asyncPool, runnable); } //方法二 public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) { return asyncRunStage(screenExecutor(executor), runnable); }
說明:
對於結果的獲取CompltableFuture類提供了四種方式
//方式一 public T get() //方式二 public T get(long timeout, TimeUnit unit) //方式三 public T getNow(T valueIfAbsent) //方式四 public T join()
說明:
示例:
public class AcquireResultTest { public static void main(String[] args) throws ExecutionException, InterruptedException { //getNow方法測試 CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(60 * 1000 * 60 ); } catch (InterruptedException e) { e.printStackTrace(); } return "hello world"; }); System.out.println(cp1.getNow("hello h2t")); //join方法測試 CompletableFuture<Integer> cp2 = CompletableFuture.supplyAsync((()-> 1 / 0)); System.out.println(cp2.join()); //get方法測試 CompletableFuture<Integer> cp3 = CompletableFuture.supplyAsync((()-> 1 / 0)); System.out.println(cp3.get()); } }
說明:
使用靜態方法建立的CompletableFuture對象無需顯示處理異常,使用new建立的對象須要調用completeExceptionally方法設置捕獲到的異常,舉例說明:
CompletableFuture completableFuture = new CompletableFuture(); new Thread(() -> { try { //doSomething,調用complete方法將其餘方法的執行結果記錄在completableFuture對象中 completableFuture.complete(null); } catch (Exception e) { //異常處理 completableFuture.completeExceptionally(e); } }).start();
店鋪爲一個列表:
private static List<Shop> shopList = Arrays.asList( new Shop("BestPrice"), new Shop("LetsSaveBig"), new Shop("MyFavoriteShop"), new Shop("BuyItAll") );
同步方法:
private static List<String> findPriceSync(String product) { return shopList.stream() .map(shop -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product))) //格式轉換 .collect(Collectors.toList()); }
異步方法:
private static List<String> findPriceAsync(String product) { List<CompletableFuture<String>> completableFutureList = shopList.stream() //轉異步執行 .map(shop -> CompletableFuture.supplyAsync( () -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product)))) //格式轉換 .collect(Collectors.toList()); return completableFutureList.stream() .map(CompletableFuture::join) //獲取結果不會拋出異常 .collect(Collectors.toList()); }
性能測試結果:
Find Price Sync Done in 4141 Find Price Async Done in 1033
異步執行效率提升四倍
在JDK1.8之前,經過調用線程池的submit方法可讓任務以異步的方式運行,該方法會返回一個Future對象,經過調用get方法獲取異步執行的結果:
private static List<String> findPriceFutureAsync(String product) { ExecutorService es = Executors.newCachedThreadPool(); List<Future<String>> futureList = shopList.stream().map(shop -> es.submit(() -> String.format("%s price is %.2f", shop.getName(), shop.getPrice(product)))).collect(Collectors.toList()); return futureList.stream() .map(f -> { String result = null; try { result = f.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } return result; }).collect(Collectors.toList()); }
既生瑜何生亮,爲何仍須要引入CompletableFuture?
對於簡單的業務場景使用Future徹底沒有,可是想將多個異步任務的計算結果組合起來,後一個異步任務的計算結果須要前一個異步任務的值等等,使用Future提供的那點API就囊中羞澀,處理起來不夠優雅,這時候仍是讓CompletableFuture以聲明式的方式優雅的處理這些需求
對前面計算結果進行處理,沒法返回新值
提供了三個方法:
//方法一 public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action) //方法二 public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) //方法三 public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
說明:
示例:
public class WhenCompleteTest { public static void main(String[] args) { CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "hello"); CompletableFuture<String> cf2 = cf1.whenComplete((v, e) -> System.out.println(String.format("value:%s, exception:%s", v, e))); System.out.println(cf2.join()); } }
將前面計算結果的的CompletableFuture傳遞給thenApply,返回thenApply處理後的結果。能夠認爲經過thenApply方法實現CompletableFuture<T>至CompletableFuture<U>的轉換。白話一點就是將CompletableFuture的計算結果做爲thenApply方法的參數,返回thenApply方法處理後的結果
提供了三個方法:
//方法一 public <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn) { return uniApplyStage(null, fn); } //方法二 public <U> CompletableFuture<U> thenApplyAsync( Function<? super T,? extends U> fn) { return uniApplyStage(asyncPool, fn); } //方法三 public <U> CompletableFuture<U> thenApplyAsync( Function<? super T,? extends U> fn, Executor executor) { return uniApplyStage(screenExecutor(executor), fn); }
說明:
示例:
public class ThenApplyTest { public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Integer> result = CompletableFuture.supplyAsync(ThenApplyTest::randomInteger).thenApply((i) -> i * 8); System.out.println(result.get()); } public static Integer randomInteger() { return 10; } }
這裏將前一個CompletableFuture計算出來的結果擴大八倍
thenApply也能夠歸類爲對結果的處理,thenAccept和thenApply的區別就是沒有返回值
提供了三個方法:
//方法一 public CompletableFuture<Void> thenAccept(Consumer<? super T> action) { return uniAcceptStage(null, action); } //方法二 public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) { return uniAcceptStage(asyncPool, action); } //方法三 public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) { return uniAcceptStage(screenExecutor(executor), action); }
說明:
示例:
public class ThenAcceptTest { public static void main(String[] args) { CompletableFuture.supplyAsync(ThenAcceptTest::getList).thenAccept(strList -> strList.stream() .forEach(m -> System.out.println(m))); } public static List<String> getList() { return Arrays.asList("a", "b", "c"); } }
將前一個CompletableFuture計算出來的結果打印出來
thenCompose方法能夠將兩個異步操做進行流水操做
提供了三個方法:
//方法一 public <U> CompletableFuture<U> thenCompose( Function<? super T, ? extends CompletionStage<U>> fn) { return uniComposeStage(null, fn); } //方法二 public <U> CompletableFuture<U> thenComposeAsync( Function<? super T, ? extends CompletionStage<U>> fn) { return uniComposeStage(asyncPool, fn); } //方法三 public <U> CompletableFuture<U> thenComposeAsync( Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) { return uniComposeStage(screenExecutor(executor), fn); }
說明:
示例:
public class ThenComposeTest { public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Integer> result = CompletableFuture.supplyAsync(ThenComposeTest::getInteger) .thenCompose(i -> CompletableFuture.supplyAsync(() -> i * 10)); System.out.println(result.get()); } private static int getInteger() { return 666; } private static int expandValue(int num) { return num * 10; } }
執行流程圖:
thenCombine方法將兩個無關的CompletableFuture組合起來,第二個Completable並不依賴第一個Completable的結果
提供了三個方法:
//方法一 public <U,V> CompletableFuture<V> thenCombine( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) { return biApplyStage(null, other, fn); } //方法二 public <U,V> CompletableFuture<V> thenCombineAsync( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) { return biApplyStage(asyncPool, other, fn); } //方法三 public <U,V> CompletableFuture<V> thenCombineAsync( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor) { return biApplyStage(screenExecutor(executor), other, fn); }
說明:
示例:
public class ThenCombineTest { private static Random random = new Random(); public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Integer> result = CompletableFuture.supplyAsync(ThenCombineTest::randomInteger).thenCombine( CompletableFuture.supplyAsync(ThenCombineTest::randomInteger), (i, j) -> i * j ); System.out.println(result.get()); } public static Integer randomInteger() { return random.nextInt(100); } }
將兩個線程計算出來的值作一個乘法在返回
執行流程圖:
方法介紹:
//allOf public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) { return andTree(cfs, 0, cfs.length - 1); } //anyOf public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) { return orTree(cfs, 0, cfs.length - 1); }
說明:
示例:
allOf方法測試
public class AllOfTest { public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Void> future1 = CompletableFuture.supplyAsync(() -> { System.out.println("hello"); return null; }); CompletableFuture<Void> future2 = CompletableFuture.supplyAsync(() -> { System.out.println("world"); return null; }); CompletableFuture<Void> result = CompletableFuture.allOf(future1, future2); System.out.println(result.get()); } }
allOf方法沒有返回值,適合沒有返回值而且須要前面全部任務執行完畢才能執行後續任務的應用場景
anyOf方法測試
public class AnyOfTest { private static Random random = new Random(); public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { randomSleep(); System.out.println("hello"); return "hello";}); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> { randomSleep(); System.out.println("world"); return "world"; }); CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2); System.out.println(result.get()); } private static void randomSleep() { try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) { e.printStackTrace(); } } }
兩個線程都會將結果打印出來,可是get方法只會返回最早完成任務的結果。該方法比較適合只要有一個返回值就能夠繼續執行其餘任務的應用場景
不少方法都提供了異步實現【帶async後綴】,可是需當心謹慎使用這些異步方法,由於異步意味着存在上下文切換,可能性能不必定比同步好。若是須要使用異步的方法,先作測試,用測試數聽說話!!!
存在IO密集型的任務能夠選擇CompletableFuture,IO部分交由另一個線程去執行。Logback、Log4j2異步日誌記錄的實現原理就是新起了一個線程去執行IO操做,這部分能夠以CompletableFuture.runAsync(()->{ioOperation();})的方式去調用,有關Logback異步日誌記錄的原理能夠參考這篇文章Logback異步日誌記錄
supplyAsync執行任務底層實現:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) { return asyncSupplyStage(asyncPool, supplier); } static <U> CompletableFuture<U> asyncSupplyStage(Executor e, Supplier<U> f) { if (f == null) throw new NullPointerException(); CompletableFuture<U> d = new CompletableFuture<U>(); e.execute(new AsyncSupply<U>(d, f)); return d; }
底層調用的是線程池去執行任務,而CompletableFuture中默認線程池爲ForkJoinPool
private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
ForkJoinPool線程池的大小取決於CPU的核數。以前寫的爲何阿里巴巴要禁用Executors建立線程池?文章中說起過,CPU密集型任務線程池大小配置爲CPU核心數就能夠了,可是IO密集型,線程池的大小由CPU數量 CPU利用率 (1 + 線程等待時間/線程CPU時間)肯定。而CompletableFuture的應用場景就是IO密集型任務,所以默認的ForkJoinPool通常沒法達到最佳性能,咱們需本身根據業務建立線程池
最後附:示例代碼,歡迎Fork與Star
併發相關
1.爲何阿里巴巴要禁用Executors建立線程池?
2.本身的事情本身作,線程異常處理
設計模式相關:
1. 單例模式,你真的寫對了嗎?
2. (策略模式+工廠模式+map)套餐 Kill 項目中的switch case
JAVA8相關:
1. 使用Stream API優化代碼
2. 親,建議你使用LocalDateTime而不是Date哦
數據庫相關:
1. mysql數據庫時間類型datetime、bigint、timestamp的查詢效率比較
2. 很高興!終於踩到了慢查詢的坑
高效相關:
1. 擼一個Java腳手架,一統團隊項目結構風格
日誌相關:
1. 日誌框架,選擇Logback Or Log4j2?
2. Logback配置文件這麼寫,TPS提升10倍
工程相關:
1. 閒來無事,動手寫一個LRU本地緩存
2. Redis實現點贊功能模塊
3. JMX可視化監控線程池
4. 權限管理 【SpringSecurity篇】
5. Spring自定義註解從入門到精通
6. java模擬登錄優酷
7. QPS這麼高,那就來寫個多級緩存吧
8. java使用phantomjs進行截圖