Java異步編程七種實現(xiàn)方法,最后一種非常強(qiáng)大
環(huán)境:java21
1. 簡介
異步編程是一種編程范式,旨在提高程序的響應(yīng)性和性能,尤其在處理耗時操作時顯得尤為重要。它通過允許程序在等待某些操作(如I/O操作、網(wǎng)絡(luò)請求或數(shù)據(jù)庫查詢)完成時繼續(xù)執(zhí)行其他任務(wù),從而優(yōu)化資源利用。異步編程避免了傳統(tǒng)同步編程中的阻塞問題,使得程序更加高效和流暢。異步編程現(xiàn)已成為處理并發(fā)和I/O密集型任務(wù)的重要手段。
本篇文章中我們將介紹在Java中實現(xiàn)異步編程的7種方法,這其中會涉及到幾個非常優(yōu)秀的第三方類庫。
2. 異步編程
2.1 Thread
Thread類是Java中用于創(chuàng)建和管理線程的核心類。通過繼承Thread類或?qū)崿F(xiàn)Runnable接口,可以創(chuàng)建新的線程。Thread類提供了線程的啟動、運行、中斷、優(yōu)先級設(shè)置等方法,是實現(xiàn)多線程編程的基礎(chǔ)。
隨著Java 8中引入lambda表達(dá)式,代碼變得更加清晰和易讀,如下示例,在一個新線程中打印一個數(shù)的階乘。
public class FactorialThread extends Thread {
private int number;
public FactorialThread(int number, String name) {
super(name) ;
this.number = number;
}
// 計算階乘的方法
private long factorial(int n) {
long result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
@Override
public void run() {
long result = factorial(number);
System.out.println(Thread.currentThread().getName() + " -> " + number + " 的階乘是: " + result);
}
public static void main(String[] args) {
// 創(chuàng)建并啟動線程,計算并打印5的階乘
Thread t1 = new FactorialThread(5, "T1");
t1.start();
// 可以創(chuàng)建更多線程來計算其他數(shù)的階乘
Thread t2 = new FactorialThread(7, "T2");
t2.start();
}
}
輸出結(jié)果
T1 -> 5 的階乘是: 120
T2 -> 7 的階乘是: 5040
我們還可以直接通過lambda計算:
Thread t3 = new Thread(() -> {
int num = 8 ;
int result = 1 ;
for (int i = 1; i <= num; i++) {
result *= i ;
}
System.out.println(Thread.currentThread().getName() + " -> " + num + " 的階乘是: " + result);
}, "T3") ;
t3.start() ;
通過lambda使得程序更加的簡潔易懂。
2.2 FutureTask
從Java 5起,F(xiàn)uture接口就提供了一種通過FutureTask執(zhí)行異步操作的方式。我們可以利用ExecutorService的submit方法來異步執(zhí)行任務(wù),并返回FutureTask的實例。下面通過FutureTask計算階乘。
try (ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10))) {
int num = 8 ;
// 返回FutureTask實例
Future<Integer> task = executor.submit(() -> {
int result = 1 ;
for (int i = 1; i <= num; i++) {
result *= i ;
}
return result ;
}) ;
// 獲取執(zhí)行結(jié)果
Integer result = task.get() ;
System.out.printf("%s -> %s 的階乘是: %s%n", Thread.currentThread().getName(), num, result);
executor.shutdown() ;
}
Future還提供了isDone方法,我們可以調(diào)用該方法判斷當(dāng)前是否執(zhí)行完成。
if (!task.isDone()) {
System.err.println("還未執(zhí)行完成") ;
}
上面的get方法調(diào)用會將當(dāng)前線程阻塞住,直到返回結(jié)果或者拋出異常。
Future<Integer> task = executor.submit(() -> {
// ...
System.out.println(1 / 0) ;
return result ;
}) ;
若將程序修改為上面,如果我們沒有調(diào)用Future#get方法,那么我們將無法得知程序拋出了異常。
2.3 CompletableFuture
CompletableFuture是Java 8引入的一個類,它結(jié)合了Future和CompletionStage的特點,提供了強(qiáng)大的異步編程能力。通過豐富的API,它可以異步執(zhí)行任務(wù)、處理結(jié)果、組合多個異步操作,并支持異常處理,極大地簡化了異步編程的復(fù)雜性。下面通過該類實現(xiàn)階乘計算。
final int num = 10 ;
CompletableFuture<Long> cf = CompletableFuture.supplyAsync(() -> {
long result = 1 ;
for (int i = 1; i <= num; i++) {
result *= i ;
}
return result ;
}) ;
Long result = cf.get() ;
System.out.printf("%s -> %s 的階乘是: %s%n", Thread.currentThread().getName(), num, result);
我們不需要顯式地使用ExecutorService。CompletableFuture內(nèi)部使用ForkJoinPool來異步處理任務(wù)。因此,它使我們的代碼更加簡潔。
我們還可以通過調(diào)用join方法來等待異步線程的執(zhí)行完成。
CompletableFuture.runAsync(() -> {
System.out.printf("%s - %s 開始執(zhí)行任務(wù)%n", Thread.currentThread().getName(), System.currentTimeMillis()) ;
try {
// 模擬耗時任務(wù)
TimeUnit.SECONDS.sleep(2) ;
} catch (InterruptedException e) {}
System.out.printf("%s - %s 任務(wù)執(zhí)行完成%n", Thread.currentThread().getName(), System.currentTimeMillis()) ;
}).join() ;
輸出結(jié)果
ForkJoinPool.commonPool-worker-1 - 1738806351732 開始執(zhí)行任務(wù)
ForkJoinPool.commonPool-worker-1 - 1738806353742 任務(wù)執(zhí)行完成
2.4 Guava
Guava提供了ListenableFuture類來執(zhí)行異步操作,允許注冊回調(diào)函數(shù)以處理操作完成時的結(jié)果或異常,增強(qiáng)了Future的功能。
引入依賴
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.0-jre</version>
</dependency>
還是以計算階乘為例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)) ;
ListeningExecutorService les = MoreExecutors.listeningDecorator(executor);
int num = 8 ;
ListenableFuture<Long> future = les.submit(() -> {
long result = 1 ;
for (int i = 1; i <= num; i++) {
result *= i ;
}
return result ;
}) ;
System.out.printf("%s -> %s 的階乘是: %s%n", Thread.currentThread().getName(), num, future.get());
我們還可以通過注冊回調(diào)的機(jī)制來獲取結(jié)果
future.addListener(() -> {
System.err.printf("%s - 計算完成%n", Thread.currentThread().getName()) ;
try {
System.out.printf("%s -> %s 的階乘是: %s%n", Thread.currentThread().getName(), num, future.get());
} catch (InterruptedException | ExecutionException e) {
}
}, executor) ;
輸出結(jié)果
pool-1-thread-2 - 計算完成
pool-1-thread-2 -> 8 的階乘是: 40320
當(dāng)Future執(zhí)行完成后,會自動執(zhí)行注冊的監(jiān)聽器,你也可以注冊多個監(jiān)聽器(但是不保證執(zhí)行的順序)。
2.5 EA Async
ea-async庫允許以順序方式編寫異步(非阻塞)代碼。因此,它使得異步編程變得更加容易,并且能夠自然地擴(kuò)展。
引入依賴
<dependency>
<groupId>com.ea.async</groupId>
<artifactId>ea-async</artifactId>
<version>1.2.3</version>
</dependency>
注:多年未更新了。
static {
Async.init();
}
public static long factorial(long num) {
long result = 1 ;
for (int i = 1; i <= num; i++) {
result *= i ;
}
return result ;
}
public static void main(String[] args) {
final long num = 10 ;
CompletableFuture<Long> completableFuture = CompletableFuture.supplyAsync(() -> factorial(num));
long result = Async.await(completableFuture) ;
System.out.printf("%s -> %s 的階乘是: %s%n", Thread.currentThread().getName(), num, result) ;
}
Async#await這個方法的行為類似于 CompletableFuture.join(),但實際上它會使調(diào)用者返回一個承諾(promise)而不是阻塞。
注:當(dāng)前必須在JDK11中運行。
2.6 Cactoos
Cactoos 是一組面向?qū)ο蟮?Java 基本元素集合。cactoos出現(xiàn)的動機(jī):對 JDK、Guava 和 Apache Commons 并不滿意,因為它們是過程式的,而不是面向?qū)ο蟮摹K鼈兡芡瓿晒ぷ?,但主要是通過靜態(tài)方法來實現(xiàn)的。Cactoos 建議幾乎做同樣的事情,但要通過對象來實現(xiàn)。
引入依賴
<dependency>
<groupId>org.cactoos</groupId>
<artifactId>cactoos</artifactId>
<version>0.56.1</version>
</dependency>
使用該庫計算階乘:
public static long factorial(long num) {
long result = 1 ;
for (int i = 1; i <= num; i++) {
result *= i ;
}
System.err.println("當(dāng)前執(zhí)行線程: " + Thread.currentThread().getName()) ;
return result ;
}
public static void main(String[] args) throws Exception {
final long num = 10 ;
Async<Long, Long> asyncFunction = new Async<>(input -> factorial(input)) ;
Future<Long> asyncFuture = asyncFunction.apply(num);
long result = asyncFuture.get() ;
System.out.printf("%s -> %s 的階乘是: %s%n", Thread.currentThread().getName(), num, result);
}
輸出結(jié)果:
當(dāng)前執(zhí)行線程: pool-1-thread-1
main -> 10 的階乘是: 3628800
在Async構(gòu)造函數(shù)中,默認(rèn)會創(chuàng)建Executors.newSingleThreadExecutor線程池。
Cactoos 不僅僅是進(jìn)行異步編程的庫,它還提供了其它非常多的功能,如下示例:
讀取文件
String text = new TextOf(
new File("d:\\pack.txt")
).asString();
文本格式化
String name = "Spring Boot實戰(zhàn)案例100例" ;
String content = new FormattedText(
"如何快速提升Spring技能, 必須學(xué)習(xí):%s",
name
).asString() ;
詳細(xì)更多的示例查看如下鏈接:https://github.com/yegor256/cactoos
2.7 Jcabi-Aspects
Jcabi-Aspects 提供了 @Async 注解,通過 AspectJ AOP 切面實現(xiàn)異步編程。
引入依賴
<dependency>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-aspects</artifactId>
<version>0.26.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.20.1</version>
</dependency>
配置編譯插件
<plugin>
<groupId>com.jcabi</groupId>
<artifactId>jcabi-maven-plugin</artifactId>
<version>0.17.0</version>
<executions>
<execution>
<goals>
<goal>ajc</goal>
</goals>
</execution>
</executions>
</plugin>
計算階乘示例:
@Async
public void task(long num) {
long result = 1 ;
for (int i = 1; i <= num; i++) {
result *= i ;
}
System.err.printf("%s - 計算結(jié)果: %s%n" , Thread.currentThread().getName(), result) ;
}
public static void main(String[] args) throws Exception {
JcabiAspectTest at = new JcabiAspectTest() ;
at.task(10L) ;
System.in.read() ;
}
輸出結(jié)果:
jcabi-async - 計算結(jié)果: 3628800
配置了jcabi-maven-plugin插件后,在編譯階段會修改對應(yīng)的字節(jié)碼進(jìn)行代碼的增強(qiáng)。
jcabi-aspects庫不止異步功能,它還提供了其它非常強(qiáng)大的功能。如下的日志記錄功能:
@Async
@Loggable
public void task(long num) {
// ...
}
當(dāng)task方法執(zhí)行時,根據(jù)你當(dāng)前日志配置的級別會輸出相應(yīng)的日志信息,如下:
圖片
最后:強(qiáng)烈推薦jcabi庫,功能多又強(qiáng)大。