多線程回顧
多線程實現的4種方式
1. 繼承 Thread
類
通過繼承 Thread
類并重寫 run()
方法實現多線程。
public class MyThread extends Thread {@Overridepublic void run() {System.out.println("線程運行: " + Thread.currentThread().getName());}
}// 使用
public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 啟動線程
}
特點:
- 缺點:Java 單繼承的限制,無法再繼承其他類。
- 適用場景:簡單任務,無需共享資源。
2. 實現 Runnable
接口
實現 Runnable
接口,將任務邏輯寫在 run()
方法中。
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("線程運行: " + Thread.currentThread().getName());}
}// 使用
public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();
}
特點:
- 優點:避免單繼承限制,適合資源共享(如多個線程處理同一任務)。
- 推薦場景:大多數情況下優先使用。
3. 實現 Callable
接口 + Future
通過 Callable
允許返回結果和拋出異常,結合 Future
或 FutureTask
獲取異步結果。
import java.util.concurrent.*;public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "執行結果: " + Thread.currentThread().getName();}
}// 使用
public static void main(String[] args) throws Exception {ExecutorService executor = Executors.newSingleThreadExecutor();Future<String> future = executor.submit(new MyCallable());System.out.println(future.get()); // 阻塞獲取結果executor.shutdown();
}
特點:
- 優點:支持返回值和異常處理。
- 適用場景:需要獲取線程執行結果的場景。
4. 使用線程池(Executor
框架)
通過 Executors
工具類創建線程池,統一管理線程資源。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDemo {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);for (int i = 0; i < 5; i++) {executor.execute(() -> {System.out.println("線程運行: " + Thread.currentThread().getName());});}executor.shutdown();}
}
特點:
- 優點:降低資源消耗,提高線程復用率,支持任務隊列和拒絕策略。
- 推薦場景:生產環境首選,高并發任務處理。
對比與建議
方式 | 返回值 | 異常處理 | 靈活性 | 資源消耗 |
---|---|---|---|---|
繼承 Thread | 不支持 | 有限 | 低 | 高 |
實現 Runnable | 不支持 | 有限 | 高 | 低 |
實現 Callable | 支持 | 支持 | 高 | 中 |
線程池(Executor ) | 支持 | 支持 | 最高 | 最低 |
建議:
- 優先選擇 實現
Runnable
/Callable
接口,避免繼承局限性。 - 生產環境務必使用 線程池,提升性能并確保穩定性。
- 需要結果時使用
Callable
+Future
,簡單任務用Runnable
。
線程池ExecutorService的7大參數
線程池構造函數
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler
)
參數說明
1. 核心線程數(corePoolSize)
- 作用:線程池中始終保持存活的線程數量(即使空閑)。
- 特點:
- 默認情況下,核心線程在空閑時不會銷毀(除非設置
allowCoreThreadTimeOut(true)
)。
- 默認情況下,核心線程在空閑時不會銷毀(除非設置
2. 最大線程數(maximumPoolSize)
- 作用:線程池允許創建的最大線程數(包括核心線程和非核心線程)。
- 規則:
- 當任務隊列已滿且當前線程數小于最大線程數時,會創建新線程處理任務。
3. 線程存活時間(keepAliveTime + unit)
- 作用:非核心線程空閑時的存活時間。
unit
為時間單位 - 規則:
- 非核心線程在空閑時間超過
keepAliveTime
后會被銷毀。 - 如果
allowCoreThreadTimeOut(true)
,核心線程也會受此時間限制。
- 非核心線程在空閑時間超過
4. 任務隊列(workQueue)
- 作用:用于存放待執行任務的阻塞隊列。
- 常見隊列類型:
- 無界隊列:如
LinkedBlockingQueue
(默認無界,可能導致 OOM)。 - 有界隊列:如
ArrayBlockingQueue
(需指定容量)。 - 同步移交隊列:如
SynchronousQueue
(不存儲任務,直接移交線程)。
- 無界隊列:如
5. 線程工廠(threadFactory)
- 作用:自定義線程的創建方式(如命名、優先級、是否為守護線程等)。
- 默認實現:
Executors.defaultThreadFactory()
。
6. 拒絕策略(handler)
- 作用:當任務隊列已滿且線程數達到最大時,如何處理新提交的任務。
- 常見策略:
AbortPolicy
(默認):拋出RejectedExecutionException
異常,且不會靜默丟棄任務CallerRunsPolicy
:由提交任務的線程直接執行任務。DiscardPolicy
:靜默丟棄新任務。DiscardOldestPolicy
:丟棄隊列中最舊的任務,嘗試重新提交新任務。
運行流程
1. 線程池創建,準備好 core 數量的核心線程,準備接受任務2. 新的任務進來,用 core 準備好的空閑線程執行。(1)、core滿了,就將再進來的任務放入阻塞隊列中。空閑的 core 就會自己去阻塞隊列獲取任務執行(2)、阻塞隊列滿了,就直接開新線程執行,最大只能開到max指定的數量(3)、max都執行好了。Max-core 數量空閑的線程會在 keepAliveTime指定的時間后自動銷毀。最終保持到 core 大小(4)、如果線程數開到了 max的數量,還有新任務進來,就會使用 reject 指定的拒絕策略進行處理3. 所有的線程創建都是由指定的 factory 創建的。
- 優先級順序:核心線程 → 任務隊列 → 非核心線程 → 拒絕策略。
- 非核心線程:僅在隊列滿時創建,空閑超時后銷毀。
- 隊列選擇:
- 無界隊列:可能導致 OOM(如
LinkedBlockingQueue
)。 - 同步隊列:適合高并發快速響應(如
SynchronousQueue
)。
- 無界隊列:可能導致 OOM(如
常見面試問題:
一個線程池中core 7; max 20; quue 50, 100個并發進來怎么分配:
7個會被立即執行,50個進入阻塞隊列,再開13個線程進行執行,剩下的30個就使用拒絕策略
常見4種線程池
A、newCachedThreadPool
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
core是0,所有都可回收
B、newFixedThreadPool
創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。
固定大小,core=max;都不可回收
C、newScheduledThreadPool
創建一個定長線程池,支持定時及周期性任務執行。
定時任務的線程池
D、newSingleThreadExecutor
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序 (FIFO、LIFO、優先級) 執行。
單線程的線程池,后臺從隊列里面獲取任務,挨個執行
為何實際開發中使用線程池
-
降低資源的消耗
通過重復利用已經創建好的線程降低線程的創建和銷毀帶來的損耗
-
提高響應速度
因為線程池中的線程數沒有超過線程池的最大上限時,有的線程處于等待分配任務的狀態,當任務來時無需創建新的線程就能執行
-
提高線程的可管理性
線程池會根據當前系統特點對池內的線程進行優化處理,減少創建和銷毀線程帶來的系統開銷。無限的創建和銷毀線程不僅消耗系統資源,還降低系統的穩定性,使用線程池進行統一分配
CompletableFuture異步編排
1. 簡介 & 業務場景
Future 是 Java 5 添加的類,用來描述一個異步計算的結果。可以使用isDone方法檢查計算是否完成,或者使用get阻塞住調用線程,直到計算完成返回結果,也可以使用cancel 方法停止任務的執行。
雖然Future以及相關使用方法提供了異步執行任務的能力,但是對于結果的獲取卻是很不方便,只能通過阻塞或者輪詢的方式得到任務的結果。阻塞的方式顯然和我們的異步編程的初衷相違背,輪詢的方式又會耗費無謂的 CPU 資源,而且也不能及時地得到計算結果,為什么不能用觀察者設計模式當計算結果完成及時通知監聽者呢?
CompletableFuture 是 Java 8
引入的一個異步編程工具,位于 java.util.concurrent
包中。它結合了 Lambda 表達式以及豐富的 API,使得編寫、組合和管理異步任務變得更加簡潔和靈活。
- 主要特點:
- 非阻塞執行:在等待 I/O 或耗時操作時,不會占用主線程,充分利用系統資源。
- 任務組合:支持將多個任務串行化、并行組合或者以其他靈活的方式進行協同工作。
- 異常處理:內置了異常感知與處理機制,可以在任務執行過程中捕獲并處理異常。
- 靈活的回調機制:通過豐富的回調方法,可以在任務完成后進行進一步處理。
- 業務場景:
- 高并發場景:如 Web 應用中同時處理大量請求時,通過異步調用提高吞吐量。
- 分布式系統:在微服務架構下,多個服務之間的異步通信和數據聚合。
- I/O 密集型任務:例如文件讀寫、網絡請求、數據庫操作等,異步化可以避免線程阻塞。
- 復雜業務流程:當多個任務存在依賴關系或者需要并行處理后再組合結果時,CompletableFuture 能夠簡化代碼邏輯。
谷粒商城中商品詳情頁的邏輯較為復雜,涉及遠程調用
假如商品詳情頁的每個查詢,需要如上標注的事件才能完成,那么用戶需要5.5秒之后才能看到商品詳情頁的內容,這顯然是不可接受的,但是如果有多個線程同時完成這6步操作,也許可以在1.5s內響應
2. 啟動異步任務
啟動異步任務主要有兩種常用方法:
-
supplyAsync
用于啟動有返回結果的異步任務。典型用法如下:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 執行耗時操作,比如調用遠程服務、數據庫查詢等return "任務結果"; });
-
runAsync
用于啟動沒有返回結果的異步任務。例如:CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {// 執行任務,但不需要返回結果 });
-
自定義線程池
可以通過傳入自定義的Executor
來管理線程資源,避免使用默認的 ForkJoinPool,從而更好地控制線程數和任務調度:ExecutorService executor = Executors.newFixedThreadPool(10); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 耗時任務return "結果"; }, executor);
3. 回調與異常感知
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 CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
whenComplete 可以處理正常和異常的計算結果,exceptionally 處理異常情況。
whenComplete 和 whenCompleteAsync 的區別:
whenComplete:是執行當前任務的線程繼續執行 whenComplete 的任務。whenCompleteAsync:是執行把 whenCompleteAsync 這個任務繼續提交給線程池來進行執行。
方法不以 Async 結尾,意味著 Action 使用相同的線程執行,而 以Async 結尾可能會使用其他線程執行 (如果是使用相同的線程池,也可能會被同一個線程選中執行)。
-
whenComplete
無論任務正常還是異常結束,都可以在此方法中進行后續處理。其回調中可以同時獲得任務的結果和異常信息:future.whenComplete((result, exception) -> {if (exception != null) {// 異常處理邏輯} else {// 正常處理邏輯} });
-
exceptionally
用于捕獲任務執行過程中出現的異常,并提供一個默認返回值:CompletableFuture<String> futureWithFallback = future.exceptionally(e -> "默認結果");
-
4. handle 最終處理
handle
方法是一個綜合性的處理方式,可以同時處理正常結果與異常情況,其回調接收兩個參數:上一步的結果和異常對象。你可以在 handle
中根據情況返回一個新的值,用于后續處理。
CompletableFuture<String> handledFuture = future.handle((result, exception) -> {if (exception != null) {// 出現異常時返回默認值return "默認結果";}// 正常時返回經過處理的結果,比如轉換為大寫return result.toUpperCase();
});
與 whenComplete
不同的是,handle
的返回值可以作為后續任務的輸入,從而實現統一的結果處理。
5. 線程串行化
線程串行化
是指多個任務按照一定順序依次執行,前一個任務的輸出作為下一個任務的輸入。這種方式常見于需要依賴前一個步驟結果的場景:
-
thenApply
用于對上一步結果進行轉換:當一個線程依賴另一個線程是,獲取上一個人物返回的結果,并返回當前任務的返回值CompletableFuture<String> futureChain = CompletableFuture.supplyAsync(() -> "初始結果").thenApply(result -> result + " -> 處理后結果");
-
thenAccept
消費處理結果。接受任務的處理結果,并消費處理,無返回結果。CompletableFuture<String> futureChain = CompletableFuture.supplyAsync(() -> "初始結果").thenAccept();
-
thenRun
只要上面的任務執行完成,就開始執行thenRun,只是處理完任務之后,執行thenRun的后續操作
public static void main(String[] args) throws ExecutionException, InterruptedException {CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {System.out.println("當前線程:" + Thread.currentThread().getName());int i = 10 / 2;System.out.println("運行結果...." + i);return i;}, executor).thenApplyAsync(res -> {System.out.println("任務二啟動了..." + "拿到了上一步的結果:" + res);return res*2;}, executor);Integer integer = future.get();System.out.println("返回數據:"+integer);}
這種鏈式調用的方式,確保了任務間嚴格的順序執行和數據傳遞,使得編寫復雜的業務邏輯更加直觀和易于維護。
6. 線程任務組合
兩任務組合-都要完成
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor);public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,Runnable action);public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);
兩個任務必須都完成,觸發該任務。
thenCombine:組合兩個future,獲取兩個future的返回結果,并返回當前任務的返回值
thenAcceptBoth:組合兩個future,獲取兩個future任務的返回結果,然后處理任務,沒有返回值。
runAfterBoth:組合兩個future,不需要獲取future的結果,只需兩個future處理完任務后,處理該任務。
兩任務組合-一個完成
public <U> CompletableFuture<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn);public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn);public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T, U> fn,Executor executor);public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action);public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action);public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action,Executor executor);public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action);public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);
當兩個任務中,任意一個future任務完成的時候,執行任務。
applyToEither:兩個任務有一個執行完成,獲取它的返回值,處理任務并有新的返回值。
acceptEither:兩個任務有一個執行完成,獲取它的返回值,處理任務,沒有新的返回值。
runAfterEither:兩個任務有一個執行完成,不需要獲取future的結果,處理任務,也沒有返回值。
多任務組合
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);
allOf:等待所有任務完成
anyOf:只要有一個任務完成
示例代碼
package com.fancy.gulimall.search.thread;import java.util.concurrent.*;public class ThreadTest {public static ExecutorService executor = Executors.newFixedThreadPool(10);public static void main(String[] args) throws ExecutionException, InterruptedException {System.out.println("main....start....");
// CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// System.out.println("當前線程:" + Thread.currentThread().getId());
// int i = 10 / 2;
// System.out.println("運行結果:" + i);
// }, executor);/*** 方法完成后的感知* */
// CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
// System.out.println("當前線程:" + Thread.currentThread().getId());
// int i = 10 / 0;
// System.out.println("運行結果:" + i);
// return i;
// }, executor).whenComplete((res,excption)->{
// //雖然能得到異常信息,但是沒法修改返回數據。
// System.out.println("異步任務成功完成了...結果是:"+res+";異常是:"+excption);
// }).exceptionally(throwable -> {
// //可以感知異常,同時返回默認值
// return 10;
// });/*** 方法執行完成后的處理*/
// CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
// System.out.println("當前線程:" + Thread.currentThread().getId());
// int i = 10 / 4;
// System.out.println("運行結果:" + i);
// return i;
// }, executor).handle((res, thr) -> {
// if (res != null) {
// return res * 2;
// }
// if (thr != null) {
// return 0;
// }
// return 0;
// });//R apply(T t, U u);/*** 線程串行化* 1)、thenRun:不能獲取到上一步的執行結果,無返回值* .thenRunAsync(() -> {* System.out.println("任務2啟動了...");* }, executor);* 2)、thenAcceptAsync;能接受上一步結果,但是無返回值* 3)、thenApplyAsync:;能接受上一步結果,有返回值*/
// CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// System.out.println("當前線程:" + Thread.currentThread().getId());
// int i = 10 / 4;
// System.out.println("運行結果:" + i);
// return i;
// }, executor).thenApplyAsync(res -> {
// System.out.println("任務2啟動了..." + res);
//
// return "Hello " + res;
// }, executor);//void accept(T t);//R apply(T t);//future.get()/*** 兩個都完成*/
// CompletableFuture<Object> future01 = CompletableFuture.supplyAsync(() -> {
// System.out.println("任務1線程:" + Thread.currentThread().getId());
// int i = 10 / 4;
// System.out.println("任務1結束:" );
// return i;
// }, executor);
//
// CompletableFuture<Object> future02 = CompletableFuture.supplyAsync(() -> {
// System.out.println("任務2線程:" + Thread.currentThread().getId());
//
// try {
// Thread.sleep(3000);
// System.out.println("任務2結束:" );
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// return "Hello";
// }, executor);// future01.runAfterBothAsync(future02,()->{
// System.out.println("任務3開始...");
// }, executor);// void accept(T t, U u);
// future01.thenAcceptBothAsync(future02,(f1,f2)->{
// System.out.println("任務3開始...之前的結果:"+f1+"--》"+f2);
// }, executor);//R apply(T t, U u);
// CompletableFuture<String> future = future01.thenCombineAsync(future02, (f1, f2) -> {
// return f1 + ":" + f2 + " -> Haha";
// }, executor);/*** 兩個任務,只要有一個完成,我們就執行任務3* runAfterEitherAsync:不感知結果,自己沒有返回值* acceptEitherAsync:感知結果,自己沒有返回值* applyToEitherAsync:感知結果,自己有返回值*/
// future01.runAfterEitherAsync(future02,()->{
// System.out.println("任務3開始...之前的結果:");
// },executor);//void accept(T t);
// future01.acceptEitherAsync(future02,(res)->{
// System.out.println("任務3開始...之前的結果:"+res);
// },executor);
// CompletableFuture<String> future = future01.applyToEitherAsync(future02, res -> {
// System.out.println("任務3開始...之前的結果:" + res);
// return res.toString() + "->哈哈";
// }, executor);CompletableFuture<String> futureImg = CompletableFuture.supplyAsync(() -> {System.out.println("查詢商品的圖片信息");return "hello.jpg";},executor);CompletableFuture<String> futureAttr = CompletableFuture.supplyAsync(() -> {System.out.println("查詢商品的屬性");return "黑色+256G";},executor);CompletableFuture<String> futureDesc = CompletableFuture.supplyAsync(() -> {try {Thread.sleep(3000);System.out.println("查詢商品介紹");} catch (InterruptedException e) {e.printStackTrace();}return "華為";},executor);// CompletableFuture<Void> allOf = CompletableFuture.allOf(futureImg, futureAttr, futureDesc);CompletableFuture<Object> anyOf = CompletableFuture.anyOf(futureImg, futureAttr, futureDesc);anyOf.get();//等待所有結果完成// System.out.println("main....end...."+futureImg.get()+"=>"+futureAttr.get()+"=>"+futureDesc.get());System.out.println("main....end...."+anyOf.get());}
}
谷粒商城業務
業務描述
這個功能主要是滿足,在商品詳情頁面查詢時,通過一個接口去獲取商品的有關的所有信息
sku基本信息
sku圖片信息
獲取spu銷售屬性組合
獲取spu介紹
獲取spu規格參數信息
其中有些查詢是可以同時進行的,有些操作則需要在其他步驟返回結果后,拿到結果去繼續查詢,最后返回結果。
所以我們為了優化接口的加載速度可以選擇異步編排
代碼
線程池配置屬性類
package com.atguigu.gulimall.product.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@ConfigurationProperties(prefix = "gulimall.thread")
@Component
@Data
public class ThreadPoolConfigProperties {private Integer coreSize;private Integer maxSize;private Integer keepAliveTime;
}
線程池配置類
package com.atguigu.gulimall.product.config;import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;//@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {@Beanpublic ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties threadPoolConfigProperties) {return new ThreadPoolExecutor(threadPoolConfigProperties.getCoreSize(), threadPoolConfigProperties.getMaxSize(), threadPoolConfigProperties.getKeepAliveTime(), TimeUnit.SECONDS, new LinkedBlockingDeque<>(10000), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());}
}
主業務邏輯方法
@Overridepublic SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {SkuItemVo skuItemVo = new SkuItemVo();CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {// sku基本信息SkuInfoEntity info = getById(skuId);skuItemVo.setInfo(info);return info;}, executor);CompletableFuture<Void> saleAttrFuture = infoFuture.thenAcceptAsync((res) -> {// spu銷售屬性組合List<SkuItemSaleAttrVo> saleAttrsBySpuId = skuSaleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());skuItemVo.setSaleAttr(saleAttrsBySpuId);}, executor);CompletableFuture<Void> descFuture = infoFuture.thenAcceptAsync(res -> {// spu介紹SpuInfoDescEntity descEntity = spuInfoDescService.getById(res.getSpuId());skuItemVo.setDesp(descEntity);}, executor);CompletableFuture<Void> baseFuture = infoFuture.thenAcceptAsync((res) -> {// spu的規格參數List<SpuItemAttrGroupVo> groupVos = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(), res.getCatalogId());skuItemVo.setGroupAttrs(groupVos);}, executor);CompletableFuture<Void> imagesFuture = CompletableFuture.runAsync(() -> {// sku圖片信息List<SkuImagesEntity> skuImages = skuImagesService.getImagesBySkuId(skuId);skuItemVo.setImages(skuImages);}, executor);// 等待所有任務都完成CompletableFuture.allOf(infoFuture,saleAttrFuture,descFuture,baseFuture,imagesFuture).get();return skuItemVo;}