干货——Java异步编程

1.概述

随着对编写非阻塞代码的需求不断增长,我们需要异步执行代码的方法。

在本教程中,我们将介绍几种使用Java实现异步编程的方法。另外,我们将探索一些提供即用型解决方案的Java库。

2. Java中的异步编程

2.1。线

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

让我们创建一个新的线程来计算和打印数字的阶乘:

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

2.2。未来任务

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

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

因此,让我们找到一个数字的阶乘:

ExecutorService threadpool = Executors.newCachedThreadPool();
Future futureTask = threadpool.submit(() -> factorial(number));
 
while (!futureTask.isDone()) {
    System.out.println("FutureTask is not finished yet..."); 
} 
long result = futureTask.get(); 
 
threadpool.shutdown();

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

2.3。未来发展

Java 8 结合了FutureCompletionStage引入了CompletableFuture。它为异步编程提供了诸如supplyAsyncrunAsyncthenApplyAsync之类的各种方法。

因此,让我们使用CompletableFuture代替FutureTask查找数字的阶乘:

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

我们不需要显式使用ExecutorService。该CompletableFuture内部使用ForkJoinPool异步处理任务。因此,它使我们的代码更加整洁。

3.番石榴

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

首先,我们将添加最新的番石榴 Maven依赖项:


    com.google.guava
    guava
    28.2-jre

然后,让我们使用ListenableFuture查找数字的阶乘:

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

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

Guava还提供一个Futures类,该类提供诸如commitAsyncscheduleAsynctransformAsync之类的方法来链接类似于CompletableFutureListenableFutures

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

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

这里,submitAsync方法需要的参数AsyncCallable,其使用所创建的可调用类

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

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

4. EA异步

电子艺界通过ea-async库将.NET的async-await功能引入了Java生态系统。

该库允许顺序编写异步(非阻塞)代码。因此,它使异步编程更加容易并且可以自然扩展。

首先,我们将最新的ea-async Maven依赖项添加到pom.xml中


    com.ea.async
    ea-async
    1.2.3

然后,让我们使用EA的Async类提供的await方法来转换先前讨论的CompletableFuture代码:

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

在这里,我们 在静态块中调用Async.init方法以初始化Async运行时工具。

异步检测会在运行时转换代码,并将对await方法的调用重写为与使用CompletableFuture链类似的行为。

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

我们可以将–javaagent JVM参数用于编译时检测。这是Async.init方法的替代方法:

java -javaagent:ea-async-1.2.3.jar -cp  

让我们来看一下顺序编写异步代码的另一个示例。

首先,我们将执行异步使用该组合物的方法,如一些连锁经营thenComposeAsyncthenAcceptAsyncCompletableFuture类:

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

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

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

该实现类似于顺序阻塞代码。但是,await方法不会阻止代码。

如前所述,所有对await方法的调用都将由Async工具重写,以与Future.join方法类似地工作。

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

5.仙人掌

Cactoos是一个基于面向对象原理的Java库。

它是Google Guava和Apache Commons的替代方案,提供了用于执行各种操作的通用对象。

首先,让我们添加最新的仙人掌 Maven依赖项:


    org.cactoos
    cactoos
    0.43

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

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

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

在这里,应用方法执行使用操作ExecutorService.submit 方法并返回的实例未来接口**。

同样,Async类具有exec方法,该方法提供相同的功能而没有返回值。

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

6.雅卡比方面

Jcabi-Aspects 通过AspectJ AOP方面为异步编程提供Async注释。

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


    com.jcabi
    jcabi-aspects
    0.22.6

jcabi-方面库需要AspectJ运行支持。因此,我们将添加AspectJrt Maven依赖项:


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

接下来,我们将添加jcabi-maven-plugin插件,该插件使用AspectJ方面编织二进制文件。该插件提供了ajc目标,可以为我们完成所有工作:


    com.jcabi
    jcabi-maven-plugin
    0.14.1
    
        
            
                ajc
            
        
    
    
        
            org.aspectj
            aspectjtools
            1.9.1
        
        
            org.aspectj
            aspectjweaver
            1.9.1
        
    

因此,我们都准备使用AOP方面进行异步编程:

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

当我们编译代码时,该库将通过AspectJ weaving 代替@Async批注注入AOP建议,以异步执行factorialUsingAspect方法。

因此,让我们使用Maven命令编译该类:

mvn install

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)

我们可以通过检查由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))

然后,我们将类作为一个简单的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

因此,我们可以看到由异步执行任务的库创建了一个新的守护进程线程jcabi-async

同样,通过库提供的@Loggable注释启用日志记录。

7.结论

在本文中,我们已经看到了Java中异步编程的几种方法。

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

另外,我们检查了使用Guava的ListenableFutureFutures类异步执行任务的支持。最后,我们探索了jcabi-AspectJ库,该库通过其@Async批注为异步方法调用提供AOP功能。

以上就是今天想跟大家分享的内容,如果有其他的意见欢迎大家讨论,如果喜欢希望大家点个关注点个赞,以后会持续为大家更新。

你可能感兴趣的:(干货——Java异步编程)