日常開發中,我們都會用到執行緒池,一般會用execute()和submit()方法提交任務,但是當你用過CompletableFuture之后,就會發現以前的執行緒池處理任務有多難用,功能有多簡陋,CompletableFuture又是多么簡潔優雅,
要知道CompletableFuture已經隨著Java8發布7年了,還沒有過它就有點說不過去了,
今天5分鐘帶你深入淺出CompletableFuture實用教程,
1. 使用執行緒池處理任務
/**
* @author yideng
* @apiNote 執行緒池使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Integer> list = Arrays.asList(1, 2, 3);
List<Future<String>> futures = new ArrayList<>();
for (Integer key : list) {
// 2. 提交任務
Future<String> future = executorService.submit(() -> {
// 睡眠一秒,模仿處理程序
Thread.sleep(1000L);
return "結果" + key;
});
futures.add(future);
}
// 3. 獲取結果
for (Future<String> future : futures) {
try {
String result = future.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
executorService.shutdown();
}
}
輸出結果:
結果1
結果2
結果3
一般大家都會這樣使用執行緒池,但是有沒有思考過這樣使用有沒有什么問題?
反正我發現兩個比較嚴重的問題:
- 獲取結果時,呼叫的future.get()方法,會阻塞當前執行緒,直到回傳結果,大大降低性能
- 有一半的代碼在寫怎么使用執行緒,其實我們不應該關心怎么使用執行緒,更應該關注任務的處理
有沒有具體的優化方案呢?當然有了,請出來我們今天的主角CompletableFuture
2. 使用CompletableFuture重構任務處理
看一下使用CompletableFuture改造后代碼:
/**
* @author yideng
* @apiNote CompletableFuture使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Integer> list = Arrays.asList(1, 2, 3);
for (Integer key : list) {
// 2. 提交任務
CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿處理程序
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return "結果" + key;
}, executorService).whenCompleteAsync((result, exception) -> {
// 3. 獲取結果
System.out.println(result);
});;
}
executorService.shutdown();
// 由于whenCompleteAsync獲取結果的方法是異步的,所以要阻塞當前執行緒才能輸出結果
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出結果:
結果1
結果2
結果3
代碼中使用了CompletableFuture的兩個方法,
supplyAsync()方法作用是提交異步任務,有兩個傳參,任務和自定義執行緒池,
whenCompleteAsync()方法作用是異步獲取結果,也有兩個傳參,結果和例外資訊,
代碼經過CompletableFuture改造后,是多么的簡潔優雅,
提交任務也不用再關心執行緒池是怎么使用了,獲取結果也不用再阻塞當前執行緒了,
如果你比較倔強,還想同步獲取結果,可以使用whenComplete()方法,或者單獨呼叫join()方法,
join()方法配合Stream流是這樣用的:
/**
* @author yideng
* @apiNote CompletableFuture使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Integer> list = Arrays.asList(1, 2, 3);
// 2. 提交任務
List<String> results = list.stream().map(key ->
CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿處理程序
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return "結果" + key;
}, executorService))
.map(CompletableFuture::join).collect(Collectors.toList());
executorService.shutdown();
// 3. 獲取結果
System.out.println(results);
}
}
輸出結果:
[結果1,結果2,結果3]
多么的簡潔優雅啊!原來executorService.submit()這種使用執行緒池的方式,可以徹底丟掉了,
3. CompletableFuture更多妙用
3.1 等待所有任務執行完成
如果讓你實作等待所有任務執行緒執行完成,再進行下一步操作,你會怎么做?
我猜你一定會使用 執行緒池+CountDownLatch,像下面這樣:
/**
* @author yideng
* @apiNote 執行緒池和CountDownLatch使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Integer> list = Arrays.asList(1, 2, 3);
CountDownLatch countDownLatch = new CountDownLatch(list.size());
for (Integer key : list) {
// 2. 提交任務
executorService.execute(() -> {
// 睡眠一秒,模仿處理程序
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
System.out.println("結果" + key);
countDownLatch.countDown();
});
}
executorService.shutdown();
// 3. 阻塞等待所有任務執行完成
try {
countDownLatch.await();
} catch (InterruptedException e) {
}
}
}
輸出結果:
結果2
結果3
結果1
Low不Low?十年前可以這樣寫,Java8都已經發布7年了,你還不會用Java8的寫法?看一下使用CompletableFuture是怎么重構的:
/**
* @author yideng
* @apiNote CompletableFuture.allOf()方法使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Integer> list = Arrays.asList(1, 2, 3);
// 2. 提交任務,并呼叫join()阻塞等待所有任務執行完成
CompletableFuture
.allOf(
list.stream().map(key ->
CompletableFuture.runAsync(() -> {
// 睡眠一秒,模仿處理程序
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
System.out.println("結果" + key);
}, executorService))
.toArray(CompletableFuture[]::new))
.join();
executorService.shutdown();
}
}
輸出結果:
結果3
結果1
結果2
代碼看著有點亂,其實邏輯很清晰,
- 遍歷list集合,提交CompletableFuture任務,把結果轉換成陣列
- 再把陣列放到CompletableFuture的allOf()方法里面
- 最后呼叫join()方法阻塞等待所有任務執行完成
CompletableFuture的allOf()方法的作用就是,等待所有任務處理完成,
這樣寫是不是簡潔優雅了許多?
3.2 任何一個任務處理完成就回傳
如果要實作這樣一個需求,往執行緒池提交一批任務,只要有其中一個任務處理完成就回傳,
該怎么做?如果你手動實作這個邏輯的話,代碼肯定復雜且低效,有了CompletableFuture就非常簡單了,只需呼叫anyOf()方法就行了,
/**
* @author yideng
* @apiNote CompletableFuture.anyOf()方法使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Integer> list = Arrays.asList(1, 2, 3);
long start = System.currentTimeMillis();
// 2. 提交任務
CompletableFuture<Object> completableFuture = CompletableFuture
.anyOf(
list.stream().map(key ->
CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿處理程序
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return "結果" + key;
}, executorService))
.toArray(CompletableFuture[]::new));
executorService.shutdown();
// 3. 獲取結果
System.out.println(completableFuture.join());
}
}
輸出結果:
結果3
一切都是那么簡單優雅,
3.3 一個執行緒執行完成,交給另一個執行緒接著執行
有這么一個需求:
一個執行緒處理完成,把處理的結果交給另一個執行緒繼續處理,怎么實作?
你是不是想到了一堆工具,執行緒池、CountDownLatch、Semaphore、ReentrantLock、Synchronized,該怎么進行組合使用呢?AB組合還是BC組合?
別瞎想了,你寫的肯定沒有CompletableFuture好用,看一下CompletableFuture是怎么用的:
/**
* @author yideng
* @apiNote CompletableFuture執行緒接力處理示例
*/
public class ThreadDemo {
public static void main(String[] args) {
// 1. 創建執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 2. 提交任務,并呼叫join()阻塞等待任務執行完成
String result2 = CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿處理程序
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return "結果1";
}, executorService).thenApplyAsync(result1 -> {
// 睡眠一秒,模仿處理程序
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return result1 + "結果2";
}, executorService).join();
executorService.shutdown();
// 3. 獲取結果
System.out.println(result2);
}
}
輸出結果:
結果1結果2
代碼主要用到了CompletableFuture的thenApplyAsync()方法,作用就是異步處理上一個執行緒的結果,
是不是太方便了?
這么好用的CompletableFuture還有沒有其他功能?當然有,
4. CompletableFuture常用API
4.1 CompletableFuture常用API說明
-
提交任務
supplyAsync
runAsync -
接力處理
thenRun thenRunAsync
thenAccept thenAcceptAsync
thenApply thenApplyAsync
handle handleAsync
applyToEither applyToEitherAsync
acceptEither acceptEitherAsync
runAfterEither runAfterEitherAsync
thenCombine thenCombineAsync
thenAcceptBoth thenAcceptBothAsync
API太多,有點眼花繚亂,很容易分類,
帶run的方法,無入參,無返回值,
帶accept的方法,有入參,無回傳值,
帶supply的方法,無入參,有回傳值,
帶apply的方法,有入參,有回傳值,
帶handle的方法,有入參,有回傳值,并且帶例外處理,
以Async結尾的方法,都是異步的,否則是同步的,
以Either結尾的方法,只需完成任意一個,
以Both/Combine結尾的方法,必須所有都完成,
- 獲取結果
join 阻塞等待,不會拋例外
get 阻塞等待,會拋例外
complete(T value) 不阻塞,如果任務已完成,回傳處理結果,如果沒完成,則回傳傳參value,
completeExceptionally(Throwable ex) 不阻塞,如果任務已完成,回傳處理結果,如果沒完成,拋例外,
4. CompletableFuture常用API使用示例
用最常見的煮飯來舉例:
4.1 then、handle方法使用示例
/**
* @author yideng
* @apiNote then、handle方法使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("1. 開始淘米");
return "2. 淘米完成";
}).thenApplyAsync(result -> {
System.out.println(result);
System.out.println("3. 開始煮飯");
// 生成一個1~10的亂數
if (RandomUtils.nextInt(1, 10) > 5) {
throw new RuntimeException("4. 電飯煲壞了,煮不了");
}
return "4. 煮飯完成";
}).handleAsync((result, exception) -> {
if (exception != null) {
System.out.println(exception.getMessage());
return "5. 今天沒飯吃";
} else {
System.out.println(result);
return "5. 開始吃飯";
}
});
try {
String result = completableFuture.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出結果可能是:
1. 開始淘米
2. 淘米完成
3. 開始煮飯
4. 煮飯完成
5. 開始吃飯
也可能是:
1. 開始淘米
2. 淘米完成
3. 開始煮飯
java.lang.RuntimeException: 4. 電飯煲壞了,煮不了
5. 今天沒飯吃
4.2 complete方法使用示例
/**
* @author yideng
* @apiNote complete使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
return "飯做好了";
});
//try {
// Thread.sleep(1L);
//} catch (InterruptedException e) {
//}
completableFuture.complete("飯還沒做好,我點外賣了");
System.out.println(completableFuture.join());
}
}
輸出結果:
飯還沒做好,我點外賣了
如果把注釋的sleep()方法放開,輸出結果就是:
飯做好了
4.3 either方法使用示例
/**
* @author yideng
* @apiNote either方法使用示例
*/
public class ThreadDemo {
public static void main(String[] args) {
CompletableFuture<String> meal = CompletableFuture.supplyAsync(() -> {
return "飯做好了";
});
CompletableFuture<String> outMeal = CompletableFuture.supplyAsync(() -> {
return "外賣到了";
});
// 飯先做好,就吃飯,外賣先到,就吃外賣,就是這么任性,
CompletableFuture<String> completableFuture = meal.applyToEither(outMeal, myMeal -> {
return myMeal;
});
System.out.println(completableFuture.join());
}
}
輸出結果可能是:
飯做好了
也可能是:
外賣到了
學會了嗎?開發中趕快用起來吧!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/491830.html
標籤:Java
