Java异步编程七种实现方法,最后一种非常强大

开发 前端
异步编程是一种编程范式,旨在提高程序的响应性和性能,尤其在处理耗时操作时显得尤为重要。它通过允许程序在等待某些操作(如I/O操作、网络请求或数据库查询)完成时继续执行其他任务,从而优化资源利用。

环境:java21

1. 简介

异步编程是一种编程范式,旨在提高程序的响应性和性能,尤其在处理耗时操作时显得尤为重要。它通过允许程序在等待某些操作(如I/O操作、网络请求或数据库查询)完成时继续执行其他任务,从而优化资源利用。异步编程避免了传统同步编程中的阻塞问题,使得程序更加高效和流畅。异步编程现已成为处理并发和I/O密集型任务的重要手段。

本篇文章中我们将介绍在Java中实现异步编程的7种方法,这其中会涉及到几个非常优秀的第三方类库。

2. 异步编程

2.1 Thread

Thread类是Java中用于创建和管理线程的核心类。通过继承Thread类或实现Runnable接口,可以创建新的线程。Thread类提供了线程的启动、运行、中断、优先级设置等方法,是实现多线程编程的基础。

随着Java 8中引入lambda表达式,代码变得更加清晰和易读,如下示例,在一个新线程中打印一个数的阶乘。

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) {
    // 创建并启动线程,计算并打印5的阶乘
    Thread t1 = new FactorialThread(5, "T1");
    t1.start();
    // 可以创建更多线程来计算其他数的阶乘
    Thread t2 = new FactorialThread(7, "T2");
    t2.start();
  }
}

输出结果

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起,Future接口就提供了一种通过FutureTask执行异步操作的方式。我们可以利用ExecutorService的submit方法来异步执行任务,并返回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 ;
  }) ;
  // 获取执行结果
  Integer result = task.get() ;
  System.out.printf("%s -> %s 的阶乘是: %s%n", Thread.currentThread().getName(), num, result);
  executor.shutdown() ;
}

Future还提供了isDone方法,我们可以调用该方法判断当前是否执行完成。

if (!task.isDone()) {
  System.err.println("还未执行完成") ;
}

上面的get方法调用会将当前线程阻塞住,直到返回结果或者抛出异常。

Future<Integer> task = executor.submit(() -> {
  // ...
  System.out.println(1 / 0) ;
  return result ;
}) ;

若将程序修改为上面,如果我们没有调用Future#get方法,那么我们将无法得知程序抛出了异常。

2.3 CompletableFuture

CompletableFuture是Java 8引入的一个类,它结合了FutureCompletionStage的特点,提供了强大的异步编程能力。通过丰富的API,它可以异步执行任务、处理结果、组合多个异步操作,并支持异常处理,极大地简化了异步编程的复杂性。下面通过该类实现阶乘计算。

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

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

我们还可以通过调用join方法来等待异步线程的执行完成。

CompletableFuture.runAsync(() -> {
  System.out.printf("%s - %s 开始执行任务%n", Thread.currentThread().getName(), System.currentTimeMillis()) ;
  try {
    // 模拟耗时任务
    TimeUnit.SECONDS.sleep(2) ;
  } catch (InterruptedException e) {}
  System.out.printf("%s - %s 任务执行完成%n", Thread.currentThread().getName(), System.currentTimeMillis()) ;
}).join() ;

输出结果

ForkJoinPool.commonPool-worker-1 - 1738806351732 开始执行任务
ForkJoinPool.commonPool-worker-1 - 1738806353742 任务执行完成

2.4 Guava

Guava提供了ListenableFuture类来执行异步操作,允许注册回调函数以处理操作完成时的结果或异常,增强了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());

我们还可以通过注册回调的机制来获取结果

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

输出结果

pool-1-thread-2 - 计算完成
pool-1-thread-2 -> 8 的阶乘是: 40320

当Future执行完成后,会自动执行注册的监听器,你也可以注册多个监听器(但是不保证执行的顺序)。

2.5 EA Async

ea-async库允许以顺序方式编写异步(非阻塞)代码。因此,它使得异步编程变得更加容易,并且能够自然地扩展。

引入依赖

<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(),但实际上它会使调用者返回一个承诺(promise)而不是阻塞。

注:当前必须在JDK11中运行。

2.6 Cactoos

Cactoos 是一组面向对象的 Java 基本元素集合。cactoos出现的动机:对 JDK、Guava 和 Apache Commons 并不满意,因为它们是过程式的,而不是面向对象的。它们能完成工作,但主要是通过静态方法来实现的。Cactoos 建议几乎做同样的事情,但要通过对象来实现。

引入依赖

<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("当前执行线程: " + 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);
}

输出结果:

当前执行线程: pool-1-thread-1
main -> 10 的阶乘是: 3628800

Async构造函数中,默认会创建Executors.newSingleThreadExecutor线程池。

Cactoos 不仅仅是进行异步编程的库,它还提供了其它非常多的功能,如下示例:

读取文件

String text = new TextOf(
  new File("d:\\pack.txt")
).asString();

文本格式化

String name = "Spring Boot实战案例100例" ;
String content = new FormattedText(
    "如何快速提升Spring技能, 必须学习:%s",
    name
  ).asString() ;

详细更多的示例查看如下链接:https://github.com/yegor256/cactoos

2.7 Jcabi-Aspects

Jcabi-Aspects 提供了 @Async 注解,通过 AspectJ AOP 切面实现异步编程。

引入依赖

<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 - 计算结果: %s%n" , Thread.currentThread().getName(), result) ;
}
public static void main(String[] args) throws Exception {
  JcabiAspectTest at = new JcabiAspectTest() ;
  at.task(10L) ;
  System.in.read() ;
}

输出结果:

jcabi-async - 计算结果: 3628800

配置了jcabi-maven-plugin插件后,在编译阶段会修改对应的字节码进行代码的增强。

jcabi-aspects库不止异步功能,它还提供了其它非常强大的功能。如下的日志记录功能:

@Async
@Loggable
public void task(long num) {
  // ...
}

当task方法执行时,根据你当前日志配置的级别会输出相应的日志信息,如下:

图片图片


最后:强烈推荐jcabi库,功能多又强大。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2022-07-01 08:00:44

异步编程FutureTask

2024-10-21 13:05:40

2024-12-05 10:37:36

Java纯函数final

2011-12-16 14:45:36

JavaJSP

2025-01-15 10:46:23

开发JavaScript集合

2011-05-30 13:37:46

JSP

2010-09-16 17:47:49

2016-09-28 20:05:22

2014-05-13 09:56:24

数据挖掘

2010-10-15 10:02:01

Mysql表类型

2022-05-24 14:37:49

React条件渲染

2013-01-07 10:14:06

JavaJava枚举

2024-01-02 07:56:13

ReactuseEffect数据驱动 UI

2009-07-23 16:50:04

2024-07-29 08:00:00

2023-04-18 15:57:30

2022-10-27 08:09:33

2011-03-14 10:46:03

2010-06-08 09:49:45

UML元件

2023-12-22 14:27:30

点赞
收藏

51CTO技术栈公众号