簡介
異步編程是一種編程範式,允許在等待某些操作(如IO操作)完成的過程中,暫停當前線程的執行,讓出控制權,從而允許其他操作或線程併發執行。在Java中,有多種實現異步編程的方式,包括使用Future
和Callable
接口、CompletableFuture
類以及ExecutorService
等。
下面是一個簡單的Java異步編程示例,使用了Future
和Callable
接口:
import java.util.concurrent.*;
public class AsyncDemo {
public static void main(String[] args) {
// 創建一個線程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交一個異步任務
Future<Integer> future = executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 模擬耗時操作
Thread.sleep(1000);
return 42;
}
});
// 在等待結果的過程中,主線程可以繼續執行其他任務
System.out.println("Doing other work...");
try {
// 獲取異步任務的結果
Integer result = future.get();
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
// 關閉線程池
executor.shutdown();
}
}
}
常用異步編程方式
1.注入 ThreadPoolTaskExecutor
@Autowired
是 Spring
框架中的一個註解,用於自動注入依賴。在這個例子中,ThreadPoolTaskExecutor
是一個線程池任務執行器,用於執行異步任務。
優點:
- 提高系統性能:通過使用線程池,可以有效地利用系統資源,避免頻繁地創建和銷燬線程,從而提高系統性能。
- 易於管理:線程池提供了對線程的集中管理,可以方便地調整線程池的大小、優先級等參數。
- 提供任務調度功能:線程池可以根據任務的優先級、延遲時間等參數進行任務調度,確保高優先級的任務優先執行。
缺點:
- 資源消耗:線程池會佔用一定的系統資源,如內存、CPU 等,如果配置不當,可能會導致資源浪費。
- 可能導致死鎖:在多線程環境下,如果沒有正確地使用同步機制,可能會導致死鎖等問題。
場景使用:
- 異步處理:當需要執行耗時操作時,可以使用線程池將任務放入隊列中,由線程池中的線程異步執行,提高系統的響應速度。
- 定時任務:線程池可以用於執行定時任務,如定時清理緩存、定時發送郵件等。
無返回demo:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
@Component
public class TaskExecutorDemo {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public void executeTask() {
taskExecutor.execute(() -> {
// 這裏是需要執行的任務代碼
System.out.println("任務執行中...");
});
}
}
有返回demo
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
@Component
public class TaskExecutorDemo {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public Future<String> executeTask() {
return taskExecutor.submit(() -> {
// 這裏是需要執行的任務代碼
System.out.println("任務執行中...");
return "任務執行結果";
});
}
}
future.get()
方法會阻塞當前線程,直到任務執行完成。如果任務執行過程中發生異常,future.get()
方法會拋出 ExecutionException
異常。因此,在使用 future.get()
方法時,需要進行異常處理。
2.Executors.newCachedThreadPool()
ExecutorService service = Executors.newCachedThreadPool();
這行代碼創建了一個緩存線程池,它可以根據實際情況自動調整線程數量。
優點:
- 靈活性:緩存線程池可以根據任務的數量動態地創建和銷燬線程,避免了固定線程數量的浪費和資源佔用。
- 性能優化:當任務數量增加時,緩存線程池會自動增加線程數量來提高執行效率;當任務數量減少時,緩存線程池會自動減少線程數量以節省資源。
缺點:
- 資源消耗:由於緩存線程池會根據任務數量動態調整線程數量,可能會導致系統資源的過度使用和浪費。
- 線程管理複雜:緩存線程池需要對線程進行動態管理,增加了系統的複雜度和維護成本。
場景使用:
- 大量短週期任務:當有大量短週期任務需要執行時,可以使用緩存線程池來提高執行效率。
- 不確定任務數量:當任務數量不確定或者經常變化時,緩存線程池可以根據實際情況自動調整線程數量,提高系統的穩定性和性能。
代碼demo
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
// 這裏是需要執行的任務代碼
System.out.println("任務執行中...");
}
});
}
}
3. CompletableFuture
CompletableFuture
是 Java 8
引入的一個異步編程工具類,它可以幫助我們以非阻塞的方式執行任務,提高程序的執行效率。下面是 CompletableFuture
的優缺點、使用場景以及具體代碼示例:
優點:
- 異步執行:
CompletableFuture
可以異步地執行任務,不會阻塞主線程,提高了程序的執行效率。 - 鏈式調用:
CompletableFuture
支持鏈式調用,可以將多個異步任務串聯起來,形成任務流水線,提高了代碼的可讀性和可維護性。 - 異常處理:
CompletableFuture
提供了豐富的異常處理方法,可以方便地處理異步任務中的異常情況。 - 組合操作:
CompletableFuture
支持多種組合操作,如thenApply
、thenAccept
、thenCompose
等,可以方便地對異步任務的結果進行處理。
缺點:
- 學習成本:
CompletableFuture
的使用相對複雜,需要一定的學習成本。 - 錯誤處理:
CompletableFuture
的錯誤處理方式較爲繁瑣,需要手動處理異常或者使用try-catch
語句。
使用場景:
- 異步IO操作:在需要進行網絡請求、文件讀寫等耗時操作時,可以使用
CompletableFuture
進行異步處理,提高程序的執行效率。 - 併發編程:在多線程環境下,可以使用
CompletableFuture
進行任務的並行處理,提高程序的執行效率。 - 異步計算:在需要進行大量計算的場景下,可以使用
CompletableFuture
進行異步計算,提高程序的執行效率。
代碼demo
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class CompletableFutureDemo {
public static void main(String[] args) {
// 創建一個異步任務
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Hello, CompletableFuture!";
});
// 對異步任務的結果進行處理
future.thenAccept(result -> System.out.println("Result: " + result));
// 等待異步任務完成
future.join();
}
}
4. ThreadPoolExecutor
ThreadPoolExecutor
是 Java
併發編程中的一個重要組件,它可以用來創建和管理線程池。下面是 ThreadPoolExecutor
的優缺點、使用場景以及具體代碼示例:
優點:
- 資源重用:
ThreadPoolExecutor
可以複用已創建的線程,減少了線程創建和銷燬的開銷。 - 管理線程:
ThreadPoolExecutor
提供了豐富的線程管理功能,如線程池大小、任務隊列等,方便了線程的管理和維護。 - 提高性能:通過合理地配置線程池參數,可以提高程序的性能。
- 異常處理:
ThreadPoolExecutor
提供了異常處理機制,可以方便地處理線程執行過程中的異常情況。
缺點:
- 學習成本:
ThreadPoolExecutor
的使用相對複雜,需要一定的學習成本。 - 錯誤處理:
ThreadPoolExecutor
的錯誤處理方式較爲繁瑣,需要手動處理異常或者使用try-catch
語句。
使用場景:
- 高併發場景:在需要進行大量併發操作的場景下,可以使用
ThreadPoolExecutor
來提高程序的執行效率。
I- O密集型任務:在需要進行大量 IO 操作的場景下,可以使用ThreadPoolExecutor
來提高程序的執行效率。 - 定時任務:在需要進行定時任務的場景下,可以使用
ThreadPoolExecutor
來實現任務的定時執行。
demo
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
// 創建一個固定大小的線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 提交任務到線程池
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
System.out.println("Task executed by thread: " + Thread.currentThread().getName());
});
}
// 關閉線程池
executor.shutdown();
}
}
有返回值
import java.util.concurrent.*;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創建一個固定大小的線程池
private ThreadPoolExecutor threadPoolServiceGuideOpenIdByBuyerFxy = new ThreadPoolExecutor(
8, 20, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(1000),
new ThreadFactoryBuilder().setNameFormat("xxx-check-pool-%d").build(),
new ThreadPoolExecutor.DiscardOldestPolicy());
// 提交任務到線程池並獲取 Future 對象
Future<Integer> future = executor.submit(() -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
});
// 獲取任務執行結果
int result = future.get();
System.out.println("Task result: " + result);
// 關閉線程池
executor.shutdown();
}
}
這段代碼創建了一個名爲threadPoolServiceGuideOpenIdByBuyerFxy的線程池對象。該線程池具有以下配置:
核心線程數(corePoolSize):8個線程
最大線程數(maximumPoolSize):20個線程
空閒線程存活時間(keepAliveTime):10秒
時間單位(timeUnit):秒
任務隊列(workQueue):一個容量爲1000的ArrayBlockingQueue
線程工廠(threadFactory):使用ThreadFactoryBuilder設置線程名稱格式爲"fxy-item-check-pool-%d"
拒絕策略(rejectedExecutionHandler):DiscardOldestPolicy,即丟棄最舊的任務
這個線程池可以用於執行一些需要併發處理的任務,例如檢查購買者信息、處理商品等操作。
5. @Async註解
Spring
的@Async
註解是用於實現異步方法調用的。它可以將一個方法標記爲異步執行,使得該方法在單獨的線程中運行,而不會阻塞主線程。
優點:
- 提高程序的性能和響應速度:通過將耗時的操作放在單獨的線程中執行,可以避免主線程被阻塞,從而提高程序的吞吐量和響應速度。
- 簡化代碼:使用@Async註解可以簡化異步方法的編寫,無需手動創建線程或使用線程池。
- 易於管理:
Spring
框架提供了對異步方法的管理和監控,可以方便地跟蹤異步任務的狀態和結果。
缺點:
- 複雜性增加:使用
@Async
註解會增加代碼的複雜性,需要處理異步任務的生命週期和異常情況。 - 線程安全問題:異步方法可能會涉及到共享資源的訪問,需要注意線程安全問題,避免出現競態條件和數據不一致的情況。
場景使用:
- 高併發場景:對於需要處理大量請求和大量數據的應用程序,可以使用
@Async
註解來提高系統的併發能力和響應速度。 - 耗時操作:對於一些耗時較長的操作,如數據庫查詢、文件讀寫等,可以使用
@Async
註解將其異步執行,避免阻塞主線程。
具體代碼demo:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncDemo {
@Async
public void asyncMethod() {
// 異步執行的代碼邏輯
System.out.println("異步方法執行中...");
}
}
@Async線程池
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}
上述配置創建了一個名爲"asyncExecutor"的線程池,其中pool-size設置爲10,表示線程池中的線程數量爲10。你可以根據實際需求調整pool-size的值。
接下來,你可以在需要異步執行的方法上添加@Async註解,並指定使用的線程池名稱:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class AsyncDemo {
@Async("asyncExecutor")
public void asyncMethod() {
// 異步執行的代碼邏輯
System.out.println("異步方法執行中...");
}
}
6. ScheduledExecutorService
ScheduledExecutorService
是Java
中用於執行定時任務的接口,它是ExecutorService
的一個子接口。它提供了一種方便的方式來調度和執行延遲或週期性的任務。
優點:
- 靈活性:
ScheduledExecutorService
允許你以不同的時間單位來指定任務的延遲或週期執行。 - 可配置性:你可以控制線程池的大小、任務的優先級等參數,以滿足特定的需求。
- 易於使用:它提供了簡單的方法來提交和取消任務,以及獲取任務的狀態和結果。
缺點:
- 資源消耗:如果創建過多的線程或者任務數量過多,可能會導致系統資源的浪費和性能下降。
- 異常處理:在任務執行過程中可能會拋出異常,需要適當處理這些異常以避免程序崩潰。
場景使用:
- 定時任務:適用於需要按照固定的時間間隔或者特定時間點執行的任務,例如定時清理緩存、定時發送郵件等。
- 週期性任務:適用於需要週期性執行的任務,例如定期檢查系統狀態、定期備份數據等。
具體代碼demo:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
// 創建一個ScheduledExecutorService實例
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 定義一個Runnable任務
Runnable task = () -> {
System.out.println("Task executed at " + new Date());
};
// 提交任務並設置延遲執行時間
scheduledExecutorService.schedule(task, 5, TimeUnit.SECONDS);
// 關閉ScheduledExecutorService
scheduledExecutorService.shutdown();
}
}
Java異步操作的優缺點
Java異步操作的優點具體如下:
- 提高併發性:異步操作允許多個任務並行執行,這樣可以更好地利用系統資源,尤其是在多核處理器上。
- 提升響應性:在處理耗時的IO操作時,異步操作可以避免阻塞主線程,從而提高應用程序的響應速度。
Java異步操作的缺點具體如下:
- 編程複雜性:異步編程通常需要處理回調函數、狀態同步等複雜的邏輯,這可能會增加代碼的複雜性和出錯的風險。
- 線程安全:在多線程環境下,需要確保數據的一致性和線程安全,這可能需要使用額外的同步機制。
- 性能開銷:雖然異步操作可以提高併發性,但是過多的線程可能會導致上下文切換和同步的開銷,從而影響性能。
使用異步考慮
任務的性質:對於IO密集型或需要等待外部資源的任務,如網絡請求、文件讀寫等,使用異步可以顯著提高效率。然而,對於計算密集型任務,由於Java的線程模型,過多的線程可能導致上下文切換,反而降低性能。
系統資源:異步操作通常意味着更多的線程或任務在後臺運行。這可能會導致系統資源的消耗增加,如內存和CPU。因此,需要根據系統的資源情況來合理地管理異步任務的數量。
錯誤處理:在異步操作中,錯誤處理變得更加複雜。因爲操作是異步的,所以異常可能不會立即顯現。需要確保有合適的機制來捕獲和處理這些異常。
代碼複雜性:異步編程可能會使代碼邏輯更加複雜,特別是當涉及到回調函數嵌套時。這可能導致代碼難以理解和維護。
線程安全:在多線程環境下,需要特別注意數據一致性和線程安全問題。可能需要使用同步機制來保護共享數據。
調試難度:由於異步操作的非順序性,調試可能會變得困難。需要使用專門的工具和技術來跟蹤和調試異步代碼。
測試複雜性:異步代碼的測試可能更加複雜,因爲需要考慮到併發和時間依賴的因素。
框架和庫的支持:在使用異步編程時,需要確保所使用的框架和庫支持異步操作,並且能夠正確地處理異步任務。
性能考量:雖然異步可以提高性能,但過度使用或不當使用也可能導致性能問題。需要仔細評估和測試以確保異步操作帶來的性能提升。
用戶體驗:在用戶界面程序中,適當的異步操作可以避免界面凍結,提供更好的用戶體驗。