探索Java內置功能——Java異步編程

1.概述

隨著對編寫非阻塞代碼的需求不斷增長,我們需要異步執行代碼的方法。

在本教程中,我們將介紹幾種使用Java實現異步編程的方法。另外,我們將探索一些提供即用型解決方案的Java庫。

探索Java內置功能——Java異步編程


2. Java中的異步編程

2.1 線

我們可以創建一個新線程來異步執行任何操作。隨著Java 8 中lambda表達式的發佈,它變得更乾淨,更易讀。

讓我們創建一個新的線程來計算和打印數字的階乘:

<code>int number = 20;
Thread newThread = new Thread(() -> {
System.out.println("Factorial of " + number + " is: " + factorial(number));
});
newThread.start();/<code>

2.2 未來任務

從Java 5開始,Future接口提供了一種使用FutureTask執行異步操作的方法。

我們可以使用提交的方法ExecutorService的異步執行任務並返回的實例FutureTask。

因此,讓我們找到一個數字的階乘:

<code>ExecutorService threadpool = Executors.newCachedThreadPool();
Future<long> futureTask = threadpool.submit(() -> factorial(number));

while (!futureTask.isDone()) {
System.out.println("FutureTask is not finished yet...");
}
long result = futureTask.get();

threadpool.shutdown();/<long>/<code>

在這裡,我們使用了Future接口提供的isDone方法來檢查任務是否完成。完成後,我們可以使用get方法檢索結果。

2.3 未來發展

Java 8 結合了Future和CompletionStage引入了CompletableFuture。它為異步編程提供了諸如supplyAsync,runAsync和thenApplyAsync之類的各種方法。

因此,讓我們使用CompletableFuture代替FutureTask查找數字的階乘:

<code>CompletableFuture<long> completableFuture = CompletableFuture.supplyAsync(() -> factorial(number));
while (!completableFuture.isDone()) {
System.out.println("CompletableFuture is not finished yet...");
}
long result = completableFuture.get();/<long>/<code>

我們不需要顯式使用ExecutorService。該CompletableFuture內部使用ForkJoinPool異步處理任務。因此,它使我們的代碼更加整潔。

3.番石榴

Guava提供了ListenableFuture類來執行異步操作。

首先,我們將添加最新的番石榴 Maven依賴項:

<code><dependency> 

<groupid>com.google.guava/<groupid>
<artifactid>guava/<artifactid>
<version>28.2-jre/<version>
/<dependency>/<code>

然後,讓我們使用ListenableFuture查找數字的階乘:

<code>ExecutorService threadpool = Executors.newCachedThreadPool();
ListeningExecutorService service = MoreExecutors.listeningDecorator(threadpool);
ListenableFuture<long> guavaFuture = (ListenableFuture<long>) service.submit(()-> factorial(number));
long result = guavaFuture.get();/<long>/<long>/<code>

在這裡,MoreExecutors類提供了ListeningExecutorService類的實例 。然後,ListeningExecutorService.submit 方法異步執行任務,並返回ListenableFuture的實例。

Guava還提供一個Futures類,該類提供諸如commitAsync,scheduleAsync和transformAsync之類的方法來鏈接類似於CompletableFuture的ListenableFutures 。

例如,讓我們看看如何使用Futures.submitAsync 代替ListeningExecutorService.submit 方法:

<code>ListeningExecutorService service = MoreExecutors.listeningDecorator(threadpool);
AsyncCallable<long> asyncCallable = Callables.asAsyncCallable(new Callable<long>() {
public Long call() {
return factorial(number);
}
}, service);
ListenableFuture<long> guavaFuture = Futures.submitAsync(asyncCallable, service);/<long>/<long>/<long>/<code>

這裡,submitAsync方法需要的參數AsyncCallable,其使用所創建的可調用類

此外,Futures類提供了addCallback方法來註冊成功和失敗回調:

<code>Futures.addCallback( 

factorialFuture,
new FutureCallback<long>() {
public void onSuccess(Long factorial) {
System.out.println(factorial);
}
public void onFailure(Throwable thrown) {
thrown.getCause();
}
},
service);/<long>/<code>

4. EA異步

電子藝界通過ea-async庫將.NET的async-await功能引入了Java生態系統。

該庫允許順序編寫異步(非阻塞)代碼。因此,它使異步編程更加容易並且可以自然擴展。

首先,我們將最新的ea-async Maven依賴項添加到pom.xml中:

<code><dependency>
<groupid>com.ea.async/<groupid>
<artifactid>ea-async/<artifactid>
<version>1.2.3/<version>
/<dependency>/<code>

然後,讓我們使用EA的Async類提供的await方法來轉換先前討論的CompletableFuture代碼:

<code>static { 
Async.init();
}

public long factorialUsingEAAsync(int number) {
CompletableFuture<long> completableFuture = CompletableFuture.supplyAsync(() -> factorial(number));
long result = Async.await(completableFuture);
}/<long>/<code>

在這裡,我們在靜態塊中調用Async.init方法以初始化Async運行時工具。

異步檢測會在運行時轉換代碼,並將對await方法的調用重寫為與使用CompletableFuture鏈類似的行為。

因此, 對await方法的調用類似於調用Future.join。

我們可以將–javaagent JVM參數用於編譯時檢測。這是Async.init方法的替代方法:

<code>java -javaagent:ea-async-1.2.3.jar -cp <claspath> <mainclass>/<claspath>/<code>

讓我們來看一下順序編寫異步代碼的另一個示例。

首先,我們將執行異步使用該組合物的方法,如一些連鎖經營thenComposeAsync和thenAcceptAsync的CompletableFuture類:

<code>CompletableFuture<void> completableFuture = hello()
.thenComposeAsync(hello -> mergeWorld(hello))
.thenAcceptAsync(helloWorld -> print(helloWorld))
.exceptionally(throwable -> {
System.out.println(throwable.getCause());
return null;
});
completableFuture.get();/<void>/<code>

然後,我們可以使用EA的Async.await()轉換代碼:

<code>try {
String hello = await(hello());
String helloWorld = await(mergeWorld(hello));
await(CompletableFuture.runAsync(() -> print(helloWorld)));
} catch (Exception e) {
e.printStackTrace();
}/<code>

該實現類似於順序阻塞代碼。但是,await方法不會阻止代碼。

如前所述,所有對await方法的調用都將由Async工具重寫,以與Future.join方法類似地工作。

因此,一旦hello方法的異步執行完成,Future結果將傳遞到mergeWorld方法。然後,使用CompletableFuture.runAsync方法將結果傳遞到最後一次執行。

探索Java內置功能——Java異步編程


5.仙人掌

Cactoos是一個基於面向對象原理的Java庫。

它是Google Guava和Apache Commons的替代方案,提供了用於執行各種操作的通用對象。

首先,讓我們添加最新的仙人掌 Maven依賴項:

<code><dependency>
<groupid>org.cactoos/<groupid>
<artifactid>cactoos/<artifactid>
<version>0.43/<version>
/<dependency>/<code>

該庫為異步操作提供了一個Async類。

因此,我們可以使用Cactoos的Async類的實例找到數字的階乘:

<code>Async<integer> asyncFunction = new Async<integer>(input -> factorial(input));
Future<long> asyncFuture = asyncFunction.apply(number);
long result = asyncFuture.get();/<long>/<integer>/<integer>/<code>

在這裡,該應用方法執行使用操作ExecutorService.submit 方法並返回的實例未來接口。

同樣,Async類具有exec方法,該方法提供相同的功能而沒有返回值。

注意:Cactoos庫處於開發的初始階段,可能尚不適合生產使用。

6.雅卡比方面

Jcabi-Aspects 通過AspectJ AOP方面為異步編程提供Async註釋。

首先,讓我們添加最新的jcabi-aspects Maven依賴項:

<code><dependency>
<groupid>com.jcabi/<groupid>
<artifactid>jcabi-aspects/<artifactid>
<version>0.22.6/<version>
/<dependency>/<code>

該jcabi-方面庫需要AspectJ運行支持。因此,我們將添加AspectJrt Maven依賴項:

<code><dependency>
< groupId > org.aspectj groupId>
< artifactId > aspectjrt artifactId>
< version > 1.9 .5 < / version >
< / dependency>/<dependency>/<code>

接下來,我們將添加jcabi-maven-plugin插件,該插件使用AspectJ方面編織二進制文件。該插件提供了ajc目標,可以為我們完成所有工作:

<code><plugin>
<groupid>com.jcabi/<groupid>
<artifactid>jcabi-maven-plugin/<artifactid>
<version>0.14.1/<version>
<executions>
<execution>
<goals>
<goal>ajc/<goal>
/<goals>
/<execution>
/<executions>

<dependencies>
<dependency>
<groupid>org.aspectj/<groupid>
<artifactid>aspectjtools/<artifactid>
<version>1.9.1/<version>
/<dependency>
<dependency>
<groupid>org.aspectj/<groupid>
<artifactid>aspectjweaver/<artifactid>
<version>1.9.1/<version>
/<dependency>
/<dependencies>
/<plugin>/<code>

因此,我們都準備使用AOP方面進行異步編程:

<code>@Async
@Loggable
public Future<long> factorialUsingAspect(int number) {
Future<long> factorialFuture = CompletableFuture.completedFuture(factorial(number));
return factorialFuture;
}/<long>/<long>/<code>

當我們編譯代碼時,該庫將通過AspectJ weaving 代替@Async批註注入AOP建議,以異步執行factorialUsingAspect方法。

因此,讓我們使用Maven命令編譯該類:

<code>mvn install/<code>

jcabi-maven-plugin的輸出可能類似於:

<code>--- jcabi-maven-plugin:0.14.1:ajc (default) @ java-async ---
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-loggable for watching of @Loggable annotated methods
[INFO] Unwoven classes will be copied to /tutorials/java-async/target/unwoven
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-cacheable for automated cleaning of expired @Cacheable values
[INFO] ajc result: 10 file(s) processed, 0 pointcut(s) woven, 0 error(s), 0 warning(s)/<code>

我們可以通過檢查由Maven插件生成的jcabi-ajc.log文件中的日誌來驗證我們的類是否正確編織:

<code>Join point 'method-execution(java.util.concurrent.Future 
com.baeldung.async.JavaAsync.factorialUsingJcabiAspect(int))'
in Type 'com.baeldung.async.JavaAsync' (JavaAsync.java:158)
advised by around advice from 'com.jcabi.aspects.aj.MethodAsyncRunner'
(jcabi-aspects-0.22.6.jar!MethodAsyncRunner.class(from MethodAsyncRunner.java))/<code>

然後,我們將類作為一個簡單的Java應用程序運行,其輸出將類似於:

<code>17:46:58.245 [main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-loggable for watching of @Loggable annotated methods
17:46:58.355 [main] INFO com.jcabi.aspects.aj.NamedThreads -
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-async for Asynchronous method execution
17:46:58.358 [jcabi-async] INFO com.baeldung.async.JavaAsync -
#factorialUsingJcabiAspect(20): 'java.util.concurrent.CompletableFuture@14e2d7c1[Completed normally]' in 44.64µs/<code>

因此,我們可以看到由異步執行任務的庫創建了一個新的守護進程線程jcabi-async。

同樣,通過庫提供的@Loggable註釋啟用日誌記錄。

7.結論

在本文中,我們已經看到了Java中異步編程的幾種方法。

首先,我們探索了Java的內置功能,例如用於異步編程的FutureTask和CompletableFuture。然後,我們看到了一些具有開箱即用解決方案的庫,例如EA Async和Cactoos。

另外,我們檢查了使用Guava的ListenableFuture和Futures類異步執行任務的支持。最後,我們探索了jcabi-AspectJ庫,該庫通過其@Async批註為異步方法調用提供AOP功能。

探索Java內置功能——Java異步編程


最後

歡迎大家一起交流,喜歡文章的可以點贊支持轉發,以後會持續為大家更新更多內容,感謝支持!


分享到:


相關文章: