Asynchronous Programming in Java – Java中的异步编程

最后修改: 2020年 1月 11日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

With the growing demand for writing non-blocking code, we need ways to execute the code asynchronously.

随着对编写非阻塞代码的需求越来越大,我们需要有办法异步执行代码。

In this tutorial, we’ll look at a few ways to achieve asynchronous programming in Java. We’ll also explore a few Java libraries that provide out-of-the-box solutions.

在本教程中,我们将研究在 Java 中实现异步编程的几种方法。我们还将探讨一些提供开箱即用解决方案的Java库。

2. Asynchronous Programming in Java

2.Java中的异步编程

2.1. Thread

2.1.线程

We can create a new thread to perform any operation asynchronously. With the release of lambda expressions in Java 8, it’s cleaner and more readable.

我们可以创建一个新的线程来异步地执行任何操作。随着Java 8中lambda表达式的发布,它变得更干净、更易读。

Let’s create a new thread that computes and prints the factorial of a number:

让我们创建一个新线程,计算并打印一个数字的阶乘。

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

2.2. FutureTask

2.2.未来任务

Since Java 5, the Future interface provides a way to perform asynchronous operations using the FutureTask.

从Java 5开始,Future接口提供了一种使用FutureTask执行异步操作的方法。

We can use the submit method of the ExecutorService to perform the task asynchronously and return the instance of the FutureTask.

我们可以使用submit方法的ExecutorService来异步执行任务并返回FutureTask的实例。

So let’s find the factorial of a number:

所以我们来找一个数字的阶乘。

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();

Here we’ve used the isDone method provided by the Future interface to check if the task is completed. Once finished, we can retrieve the result using the get method.

这里我们使用了Future接口提供的isDone方法来检查任务是否已经完成。一旦完成,我们可以使用get方法检索结果。

2.3. CompletableFuture

2.3.可完成的未来

Java 8 introduced CompletableFuture with a combination of a Future and CompletionStage. It provides various methods like supplyAsync, runAsync, and thenApplyAsync for asynchronous programming.

Java 8引入了CompletableFuture,它结合了FutureCompletionStage它提供了各种方法,如supplyAsyncrunAsyncthenApplyAsync,用于异步编程。

Now let’s use the CompletableFuture in place of the FutureTask to find the factorial of a number:

现在让我们用CompletableFuture代替FutureTask来求一个数字的阶乘。

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

We don’t need to use the ExecutorService explicitly. The CompletableFuture internally uses ForkJoinPool to handle the task asynchronously. Thus, it makes our code a lot cleaner.

我们不需要明确地使用ExecutorServiceCompletableFuture在内部使用ForkJoinPool来异步处理任务。因此,它使我们的代码更加简洁。

3. Guava

3.番石榴

Guava provides the ListenableFuture class to perform asynchronous operations.

Guava提供ListenableFuture类来执行异步操作。

First, we’ll add the latest guava Maven dependency:

首先,我们要添加最新的guava Maven依赖。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

Then let’s find the factorial of a number using the ListenableFuture:

然后让我们用ListenableFuture来寻找一个数字的阶乘。

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

Here the MoreExecutors class provides the instance of the ListeningExecutorService class. Then the ListeningExecutorService.submit method performs the task asynchronously and returns the instance of the ListenableFuture.

这里的MoreExecutors类提供了ListeningExecutorService类的实例。然后ListeningExecutorService.submit方法异步地执行任务,并返回ListenableFuture的实例。

Guava also has a Futures class that provides methods like submitAsync, scheduleAsync, and transformAsync to chain the ListenableFutures, similar to the CompletableFuture.

Guava还有一个Futures类,它提供了像submitAsyncscheduleAsynctransformAsync这样的方法,以连锁ListenableFutures,类似于CompletableFuture。

For instance, let’s see how to use Futures.submitAsync in place of the ListeningExecutorService.submit method:

例如,让我们看看如何使用Futures.submitAsync来代替ListeningExecutorService.submit方法。

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);

Here the submitAsync method requires an argument of AsyncCallable, which is created using the Callables class.

这里submitAsync方法需要一个AsyncCallable的参数,它是使用Callables类创建的。

Additionally, the Futures class provides the addCallback method to register the success and failure callbacks:

此外,Futures类提供了addCallback方法来注册成功和失败的回调。

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

4. EA Async

4.EA异步

Electronic Arts brought the async-await feature from .NET to the Java ecosystem through the ea-async library.

Electronic Arts通过ea-async将.NET的async-await功能带入了Java生态系统。

This library allows writing asynchronous (non-blocking) code sequentially. Therefore, it makes asynchronous programming easier and scales naturally.

这个库允许按顺序编写异步(非阻塞)代码。因此,它使异步编程更容易,并能自然扩展。

First, we’ll add the latest ea-async Maven dependency to the pom.xml:

首先,我们要把最新的ea-async Maven依赖性添加到pom.xml

<dependency>
    <groupId>com.ea.async</groupId>
    <artifactId>ea-async</artifactId>
    <version>1.2.3</version>
</dependency>

Then we’ll transform the previously discussed CompletableFuture code by using the await method provided by EA’s Async class:

然后,我们将通过使用EA的await类提供的await方法来转换之前讨论的CompletableFuture代码。

static { 
    Async.init(); 
}

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

Here we make a call to the Async.init method in the static block to initialize the Async runtime instrumentation.

这里,我们在static块中调用Async.init方法,以初始化Async运行时仪表。

Async instrumentation transforms the code at runtime, and rewrites the call to the await method to behave similarly to using the chain of CompletableFuture.

Async仪器化在运行时转换代码,并重写对await方法的调用,使其行为类似于使用CompletableFuture的链。

Therefore, the call to the await method is similar to calling Future.join.

因此,await 方法的调用类似于调用Future.join.

We can use the – javaagent JVM parameter for compile-time instrumentation. This is an alternative to the Async.init method:

我们可以使用 – javaagent JVM参数来进行编译时检测。这是对Async.init方法的一种替代。

java -javaagent:ea-async-1.2.3.jar -cp <claspath> <MainClass>

Now let’s look at another example of writing asynchronous code sequentially.

现在让我们来看看另一个按顺序编写异步代码的例子。

First, we’ll perform a few chain operations asynchronously using the composition methods like thenComposeAsync and thenAcceptAsync of the CompletableFuture class:

首先,我们将使用thenComposeAsyncthenAcceptAsyncCompletableFuture类的组成方法,异步地执行一些链式操作。

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

Then we can transform the code using EA’s Async.await():

然后我们可以使用EA的Async.await()转换代码。

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

The implementation resembles the sequential blocking code; however, the await method doesn’t block the code.

该实现类似于顺序阻塞代码;但是,await方法并没有阻塞代码。

As discussed, all calls to the await method will be rewritten by the Async instrumentation to work similarly to the Future.join method.

正如所讨论的,对await方法的所有调用将由Async工具化重写,以类似于Future.join方法的方式工作。

So once the asynchronous execution of the hello method is finished, the Future result is passed to the mergeWorld method. Then the result is passed to the last execution using the CompletableFuture.runAsync method.

因此,一旦hello方法的异步执行完成,Future的结果将被传递给mergeWorld方法。然后,该结果被传递给使用CompletableFuture.runAsync方法的最后一次执行。

5. Cactoos

5.仙人掌(Cactoos

Cactoos is a Java library based on object-oriented principles.

Cactoos是一个基于面向对象原则的Java库,

It’s an alternative to Google Guava and Apache Commons that provides common objects for performing various operations.

它是谷歌Guava和Apache Commons的替代品,提供了执行各种操作的通用对象。

First, let’s add the latest cactoos Maven dependency:

首先,让我们添加最新的cactoosMaven依赖项。

<dependency>
    <groupId>org.cactoos</groupId>
    <artifactId>cactoos</artifactId>
    <version>0.43</version>
</dependency>

This library provides an Async class for asynchronous operations.

这个库为异步操作提供了一个Async类。

So we can find the factorial of a number using the instance of Cactoos’s Async class:

因此,我们可以使用Cactoos的Async类的实例找到一个数字的阶乘。

Async<Integer, Long> asyncFunction = new Async<Integer, Long>(input -> factorial(input));
Future<Long> asyncFuture = asyncFunction.apply(number);
long result = asyncFuture.get();

Here the apply method executes the operation using the ExecutorService.submit method, and returns an instance of the Future interface.

这里apply方法使用ExecutorService.submit方法执行操作,并返回一个Future接口的实例

Similarly, the Async class has the exec method that provides the same feature without a return value.

类似地,Async类有exec方法,提供同样的功能,但没有返回值。

Note: the Cactoos library is in the initial stages of development and may not be appropriate for production use yet.

注意:Cactoos库正处于开发的初始阶段,可能还不适合生产使用。

6. Jcabi-Aspects

6.Jcabi-Aspects

Jcabi-Aspects provides the @Async annotation for asynchronous programming through AspectJ AOP aspects.

Jcabi-Aspects提供了@Async注解,通过AspectJAOP方面进行异步编程。

First, let’s add the latest jcabi-aspects Maven dependency:

首先,让我们添加最新的jcabi-aspects Maven依赖。

<dependency>
    <groupId>com.jcabi</groupId>
    <artifactId>jcabi-aspects</artifactId>
    <version>0.22.6</version>
</dependency>

The jcabi-aspects library requires AspectJ runtime support, so we’ll add the aspectjrt Maven dependency:

jcabi-aspects库需要AspectJ运行时支持,因此我们将添加aspectjrt Maven依赖。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.5</version>
</dependency>

Next, we’ll add the jcabi-maven-plugin plugin that weaves the binaries with AspectJ aspects. The plugin provides the ajc goal that does all the work for us:

接下来,我们将添加jcabi-maven-plugin插件,将二进制文件与AspectJ方面结合起来。该插件提供了ajc目标,为我们完成了所有工作。

<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>

Now we’re all set to use the AOP aspects for asynchronous programming:

现在我们已经准备好使用AOP方面的异步编程了。

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

When we compile the code, the library will inject AOP advice in place of the @Async annotation through AspectJ weaving, for the asynchronous execution of the factorialUsingAspect method.

当我们编译代码时,库将通过AspectJ编织注入AOP建议以代替@Async注解,用于factorialUsingAspect方法的异步执行。

Let’s compile the class using the Maven command:

让我们用Maven命令编译该类。

mvn install

The output from the jcabi-maven-plugin may look like:

jcabi-maven-plugin的输出可能看起来像。

 --- 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)

We can verify if our class is woven correctly by checking the logs in the jcabi-ajc.log file generated by the Maven plugin:

我们可以通过检查Maven插件生成的jcabi-ajc.log文件中的日志来验证我们的类是否被正确编织了。

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))

Then we’ll run the class as a simple Java application, and the output will look like:

然后我们将该类作为一个简单的Java应用程序运行,输出结果会是这样的。

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

As we can see, a new daemon thread, jcabi-async, is created by the library that performed the task asynchronously.

我们可以看到,一个新的守护线程,jcabi-async,被异步执行任务的库所创建。

Similarly, the logging is enabled by the @Loggable annotation provided by the library.

同样地,日志记录是由库提供的@Loggable注解来启用的。

7. Conclusion

7.结语

In this article, we learned a few ways of asynchronous programming in Java.

在这篇文章中,我们学习了Java中异步编程的一些方法。

To begin with, we explored Java’s in-built features like FutureTask and CompletableFuture for asynchronous programming. Then we examined a few libraries, like EA Async and Cactoos, with out-of-the-box solutions.

首先,我们探索了Java的内置功能,如FutureTaskCompletableFuture的异步编程。然后我们研究了一些库,如EA Async和Cactoos,它们都有开箱即用的解决方案。

We also discussed the support of performing tasks asynchronously using Guava’s ListenableFuture and Futures classes. Finally, we touched on the jcabi-AspectJ library that provides AOP features through its @Async annotation for asynchronous method calls.

我们还讨论了使用Guava的ListenableFutureFutures类异步执行任务的支持。最后,我们谈到了jcabi-AspectJ库,它通过@Async注解为异步方法调用提供了AOP功能。

As usual, all the code implementations are available over on GitHub.

像往常一样,所有的代码实现都可以在GitHub上找到