Java中實現異步編程的核心在于避免主線程阻塞,提高響應速度和吞吐量,主要通過completablefuture實現。1. completablefuture提供supplyasync()和runasync()創建異步任務,前者用于有返回值的任務,后者用于無返回值的任務;2. 異常處理可通過exceptionally()返回默認值或handle()統一處理結果與異常;3. 組合多個異步任務可使用thenapply()轉換結果、thencompose()鏈式依賴任務、thencombine()合并兩個獨立任務的結果;4. 線程池選擇上,默認使用forkjoinpool.commonpool()適合cpu密集型任務,i/o密集型或需控制并發時應自定義線程池并通過參數傳入;5. 避免阻塞應避免調用get()方法,改用thenaccept()或thenapplyasync()將后續操作放入異步線程執行,確保非阻塞特性。
Java中實現異步編程,核心在于避免主線程阻塞,提高程序響應速度和吞吐量。CompletableFuture是Java 8引入的強大的異步編程工具,它提供了靈活的方式來處理異步任務的結果,組合多個異步操作,以及處理異常。
CompletableFuture的使用方法
CompletableFuture提供了多種靜態方法來創建異步任務,例如supplyAsync()、runAsync()。supplyAsync()用于執行有返回值的任務,而runAsync()用于執行沒有返回值的任務。
立即學習“Java免費學習筆記(深入)”;
// 執行一個有返回值的異步任務 CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // 模擬耗時操作 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } return "Hello, Async!"; }); // 執行一個沒有返回值的異步任務 CompletableFuture<Void> futureVoid = CompletableFuture.runAsync(() -> { // 模擬耗時操作 try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } System.out.println("Async task completed!"); }); // 獲取異步任務的結果 (會阻塞直到結果可用) try { String result = future.get(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); }
關鍵在于,supplyAsync和runAsync默認使用ForkJoinPool.commonPool()線程池。如果需要自定義線程池,可以傳入Executor參數。 這點需要注意,避免所有異步任務都擠在同一個公共線程池里。
如何處理異步任務的異常?
CompletableFuture提供了exceptionally()、handle()等方法來處理異常。exceptionally()允許你提供一個函數,在發生異常時返回一個默認值。handle()則允許你同時處理正常結果和異常情況。
CompletableFuture<String> futureWithException = CompletableFuture.supplyAsync(() -> { // 模擬可能拋出異常的操作 if (Math.random() > 0.5) { throw new RuntimeException("Something went wrong!"); } return "Success!"; }).exceptionally(ex -> { System.err.println("Exception occurred: " + ex.getMessage()); return "Default Value"; // 返回默認值 }); try { String result = futureWithException.get(); System.out.println("Result: " + result); } catch (Exception e) { e.printStackTrace(); }
handle方法更強大,它接收一個BiFunction,可以根據結果和異常進行更復雜的處理。 個人覺得,使用handle可以避免多層嵌套的try-catch,代碼更簡潔。
CompletableFuture如何組合多個異步任務?
CompletableFuture提供了thenApply()、thenCompose()、thenCombine()等方法來組合多個異步任務。thenApply()用于對異步任務的結果進行轉換。thenCompose()用于將一個異步任務的結果傳遞給另一個異步任務。thenCombine()用于合并兩個獨立的異步任務的結果。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello"); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World"); // 使用 thenCombine 合并兩個 Future 的結果 CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> s1 + ", " + s2); // 使用 thenApply 對結果進行轉換 CompletableFuture<String> transformedFuture = combinedFuture.thenApply(String::toUpperCase); try { String result = transformedFuture.get(); System.out.println(result); // 輸出: HELLO, WORLD } catch (Exception e) { e.printStackTrace(); }
thenCompose適用于一個任務的完成依賴于另一個任務的結果的場景。 例如,從數據庫查詢用戶信息,然后根據用戶信息查詢訂單信息。
CompletableFuture中的線程池選擇有什么講究?
選擇合適的線程池至關重要。 默認的ForkJoinPool.commonPool()適用于CPU密集型任務,但如果你的任務是I/O密集型,或者需要控制并發度,那么自定義線程池是更好的選擇。
// 創建一個固定大小的線程池 ExecutorService executor = Executors.newFixedThreadPool(10); CompletableFuture<String> futureWithExecutor = CompletableFuture.supplyAsync(() -> { // 模擬耗時操作 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } return "Hello from custom executor!"; }, executor); try { String result = futureWithExecutor.get(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } finally { executor.shutdown(); // 關閉線程池 }
需要注意的是,使用自定義線程池后,一定要記得關閉線程池,釋放資源。 否則,可能會導致程序無法正常退出。
如何避免CompletableFuture的阻塞?
雖然get()方法可以獲取異步任務的結果,但它會阻塞當前線程。 為了避免阻塞,可以使用thenAccept()、thenApplyAsync()等方法。 thenAccept()在異步任務完成后執行一個Consumer,而thenApplyAsync()則在另一個異步任務中執行轉換操作。
CompletableFuture<String> futureNonBlocking = CompletableFuture.supplyAsync(() -> { // 模擬耗時操作 try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } return "Non-blocking result!"; }); // 使用 thenAccept 在異步任務完成后執行一個 Consumer futureNonBlocking.thenAccept(result -> { System.out.println("Result (non-blocking): " + result); }); // 使用 thenApplyAsync 在另一個異步任務中執行轉換操作 CompletableFuture<String> transformedFutureNonBlocking = futureNonBlocking.thenApplyAsync(String::toUpperCase); transformedFutureNonBlocking.thenAccept(result -> { System.out.println("Transformed result (non-blocking): " + result); }); // 注意:這里不要調用 get(),否則就阻塞了
避免阻塞的關鍵在于,不要使用get()方法,而是使用thenAccept、thenApplyAsync等方法,將后續操作也放到異步線程中執行。 這樣才能真正實現非阻塞的異步編程。