Java 异步编程的几种方式
前言
异步编程是让程序并发运行的一种手段。它允许多个事情同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行,当方法执行完成时通知给主线程根据需要获取其执行结果或者失败异常的原因。使用异步编程可以大大提高我们程序的吞吐量,可以更好的面对更高的并发场景并更好的利用现有的系统资源,同时也会一定程度上减少用户的等待时间等。本文我们一起来看看在 Java 语言中使用异步编程有哪些方式。
Thread 方式
在 Java 语言中最简单使用异步编程的方式就是创建一个 Thread 来实现,如果你使用的 JDK 版本是 8 以上的话,可以使用 Lambda 表达式 会更加简洁。为了能更好的体现出异步的高效性,下面提供同步版本和异步版本的示例作为对照:
/**
* @author mghio
* @since 2021-08-01
*/
public class SyncWithAsyncDemo {
public static void doOneThing() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doOneThing ---->>> success");
}
public static void doOtherThing() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doOtherThing ---->>> success");
}
public synchronized static void main(String[] args) throws InterruptedException {
StopWatch stopWatch = new StopWatch("SyncWithAsyncDemo");
stopWatch.start();
// 同步调用版本
// testSynchronize();
// 异步调用版本
testAsynchronize();
stopWatch.stop();
System.out.println(stopWatch);
}
private static void testAsynchronize() throws InterruptedException {
System.out.println("-------------------- testAsynchronize --------------------");
// 创建一个线程执行 doOneThing
Thread doOneThingThread = new Thread(SyncWithAsyncDemo::doOneThing, "doOneThing-Thread");
doOneThingThread.start();
doOtherThing();
// 等待 doOneThing 线程执行完成
doOneThingThread.join();
}
private static void testSynchronize() {
System.out.println("-------------------- testSynchronize --------------------");
doOneThing();
doOtherThing();
}
}
同步执行的运行如下:

注释掉同步调用版本的代码,得到异步执行的结果如下:

从两次的运行结果可以看出,同步版本耗时 4002 ms,异步版本执行耗时 2064 ms,异步执行耗时减少将近一半,可以看出使用异步编程后可以大大缩短程序运行时间。
上面的示例的异步线程代码在 main 方法内开启了一个线程 doOneThing-Thread 用来异步执行 doOneThing 任务,在这时该线程与 main 主线程并发运行,也就是任务 doOneThing 与任务 doOtherThing 并发运行,则等主线程运行完 doOtherThing 任务后同步等待线程 doOneThing 运行完毕,整体还是比较简单的。
但是这个示例只能作为示例使用,如果用到了生产环境发生事故后果自负,使用上面这种 Thread 方式异步编程存在两个明显的问题。
- 创建线程没有复用。我们知道频繁的线程创建与销毁是需要一部分开销的,而且示例里也没有限制线程的个数,如果使用不当可能会把系统线程用尽,从而引发事故,这个问题使用线程池可以解决。
- 异步任务无法获取最终的执行结果。示例中的这种方式是满足不了的,这时候就需要使用下面介绍的第二种 FutureTask 的方式了。
FutureTask 方式
自 JDK 1.5 开始,引入了 Future 接口和实现 Future 接口的 FutureTask 类来表示异步计算结果。这个 FutureTask 类不仅实现了 Future 接口还实现了 Runnable 接口,表示一种可生成结果的 Runnable。其可以处于这三种状态:
- 未启动 当创建一个 FutureTask 没有执行 FutureTask.run() 方法之前
- 已启动 在 FutureTask.run() 方法执行的过程中
- 已完成 在 FutureTask.run() 方法正常执行结果或者调用了 FutureTask.cancel(boolean mayInterruptIfRunning) 方法以及在调用 FutureTask.run() 方法的过程中发生异常结束后
FutureTask 类实现了 Future 接口的开启和取消任务、查询任务是否完成、获取计算结果方法。要获取 FutureTask 任务的结果,我们只能通过调用 getXXX() 系列方法才能获取,当结果还没出来时候这些方法会被阻塞,同时这了任务可以是 Callable 类型(有返回结果),也可以是 Runnable 类型(无返回结果)。我们修改上面的示例把两个任务方法修改为返回 String 类型,使用 FutureTask 的方法如下:
private static void testFutureTask() throws ExecutionException, InterruptedException {
System.out.println("-------------------- testFutureTask --------------------");
// 创建一个 FutureTask(doOneThing 任务)
FutureTask<String> futureTask = new FutureTask<>(FutureTaskDemo::doOneThing);
// 使用线程池执行 doOneThing 任务
ForkJoinPool.commonPool().execute(futureTask);
// 执行 doOtherThing 任务
String doOtherThingResult = doOtherThing();
// 同步等待线程执行 doOneThing 任务结束
String doOneThingResult = futureTask.get();
// 任务执行结果输出
System.out.println("doOneThingResult ---->>> " + doOneThingResult);
System.out.println("doOtherThingResult ---->>> " + doOtherThingResult);
}
使用 FutureTask 异步编程方式的耗时和上面的 Thread 方式是差不多的,其本质都是另起一个线程去做 doOneThing 任务然后等待返回,运行结果如下:

这个示例中,doOneThing 和 doOtherThing 都是有返回值的任务(都返回 String 类型结果),我们在主线程 main 中创建一个异步任务 FutureTask 来执行 doOneThing,然后使用 ForkJoinPool.commonPool() 创建线程池(有关 ForkJoinPool 的介绍见 这里),然后调用了线程池的 execute 方法把 futureTask 提交到线程池来执行。
通过示例可以看到,虽然 FutureTask 提供了一些方法让我们获取任务的执行结果、任务是否完成等,但是使用还是比较复杂,在一些较为复杂的场景(比如多个 FutureTask 之间的关系表示)的编码还是比较繁琐,还是当我们调用 getXXX() 系列方法时还是会在任务执行完毕前阻塞调用线程,达不到异步编程的效果,基于这些问题,在 JDK 8 中引入了 CompletableFuture 类,下面来看看如何使用 CompletableFuture 来实现异步编程。
CompletableFuture 方式
JDK 8 中引入了 CompletableFuture 类,实现了 Future 和 CompletionStage 接口,为异步编程提供了一些列方法,如 supplyAsync、runAsync 和 thenApplyAsync 等,除此之外 CompletableFuture 还有一个重要的功能就是可以让两个或者多个 CompletableFuture 进行运算来产生结果。代码如下:
/**
* @author mghio
* @since 2021-08-01
*/
public class CompletableFutureDemo {
public static CompletableFuture<String> doOneThing() {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "doOneThing";
});
}
public static CompletableFuture<String> doOtherThing(String parameter) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return parameter + " " + "doOtherThing";
});
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
StopWatch stopWatch = new StopWatch("CompletableFutureDemo");
stopWatch.start();
// 异步执行版本
testCompletableFuture();
stopWatch.stop();
System.out.println(stopWatch);
}
private static void testCompletableFuture() throws InterruptedException, ExecutionException {
// 先执行 doOneThing 任务,后执行 doOtherThing 任务
CompletableFuture<String> resultFuture = doOneThing().thenCompose(CompletableFutureDemo::doOtherThing);
// 获取任务结果
String doOneThingResult = resultFuture.get();
// 获取执行结果
System.out.println("DoOneThing and DoOtherThing execute finished. result = " + doOneThingResult);
}
}
执行结果如下:

在主线程 main 中首先调用了方法 doOneThing() 方法开启了一个异步任务,并返回了对应的 CompletableFuture 对象,我们取名为 doOneThingFuture,然后在 doOneThingFuture 的基础上使用 CompletableFuture 的 thenCompose() 方法,让 doOneThingFuture 方法执行完成后,使用其执行结果作为 doOtherThing(String parameter) 方法的参数创建的异步任务返回。
我们不需要显式使用 ExecutorService,在 CompletableFuture 内部使用的是 Fork/Join 框架异步处理任务,因此,它使我们编写的异步代码更加简洁。此外,CompletableFuture 类功能很强大其提供了和很多方便的方法,更多关于 CompletableFuture 的使用请见 这篇。
总结
本文介绍了在 Java 中的 JDK 使用异步编程的三种方式,这些是我们最基础的实现异步编程的工具,在其之上的还有 Guava 库提供的 ListenableFuture 和 Futures 类以及 Spring 框架提供的异步执行能力,使用 @Async 等注解实现异步处理,感兴趣的话可以自行学习了解。
Java 异步编程的几种方式的更多相关文章
- 说说Java异步调用的几种方式
日常开发中,会经常遇到说,前台调服务,然后触发一个比较耗时的异步服务,且不用等异步任务的处理结果就对原服务进行返回.这里就涉及的Java异步调用的一个知识.下面本文尝试将Java异步调用的多种方式进行 ...
- Java 异步编程 (5 种异步实现方式详解)
同步操作如果遇到一个耗时的方法,需要阻塞等待,那么我们有没有办法解决呢?让它异步执行,下面我会详解异步及实现@mikechen 目录 什么是异步? 一.线程异步 二.Future异步 三.Comp ...
- Paip.Php Java 异步编程。推模型与拉模型。响应式(Reactive)”编程FutureData总结... 1
Paip.Php Java 异步编程.推模型与拉模型.响应式(Reactive)"编程FutureData总结... 1.1.1 异步调用的实现以及角色(:调用者 提货单) F ...
- JAVA解析XML的四种方式
java解析xml文件四种方式 1.介绍 1)DOM(JAXP Crimson解析器) DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准.DOM是以层次结构组织的节点或信息片断的集合.这 ...
- java解析xml的几种方式
java解析xml的几种方式 DOM DOM的全称是Document ObjectModel,也即文档对象模型.在应用程序中,基于DOM的XML分析器将一个XML文档转换成一个对象模型的集合(通常称D ...
- 对Java代码加密的两种方式,防止反编译
使用Virbox Protector对Java项目加密有两种方式,一种是对War包加密,一种是对Jar包加密.Virbox Protector支持这两种文件格式加密,可以加密用于解析class文件的j ...
- java异步编程降低延迟
目录 java异步编程降低延迟 一.ExecutorService和CompletionService 二.CompletableFuture(重要) 三.stream中的parallel(并行流) ...
- 当阿里面试官问我:Java创建线程有几种方式?我就知道问题没那么简单
这是最新的大厂面试系列,还原真实场景,提炼出知识点分享给大家. 点赞再看,养成习惯~ 微信搜索[武哥聊编程],关注这个 Java 菜鸟. 昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的 ...
- Java中创建对象的几种方式
Java中创建对象的五种方式: 作为java开发者,我们每天创建很多对象,但是我们通常使用依赖注入的方式管理系统,比如:Spring去创建对象,然而这里有很多创建对象的方法:使用New关键字.使用Cl ...
随机推荐
- 使用 Docker 部署 Node 应用 - 镜像文件尺寸的优化
前面 使用 Docker 部署 Node 应用 一文中完成了镜像的创建和运行,不过生成的镜像还有些粗糙,需要进一步优化. 镜像的优化 通过 docker images 看到简单的一个 node 服务端 ...
- 「模拟8.29」chinese(性质)·physics·chemistry(概率期望)
T1 chinese 根据他的问题i*f[i]我们容易联想到,答案其实是每种方案中每个点的贡献为1的加和 我们可以转变问题,每个点在所有方案的贡献 进而其实询问就是1-k的取值,有多少中方案再取个和 ...
- React-Antd4的Form表单校验
之前很少用react做项目,最近入职新公司,用的react,在自己的摸索过程中,慢慢会记录一些使用方法.今天简单记录一下使用antd 4.0版本的Form表单校验,直接代码. 需要购买阿里云产品和服务 ...
- ESCMScript(1)let和const
1.let命令 基本语法 ES6 新增了let命令,用来声明变量.它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效. { let a = 1 var b = 2 console ...
- R语言读取文件
1.R语言读取文件,文件类型为.txt 直接使用read.table()即可,若不知道当前的工作目录,可以使用函数getwd()来查看 2.R语言读取文件,文件类型为.xlsx 方法一:可以把excl ...
- Kali下切换JDK版本
Kali下自由更换JDK版本 今天在学习了一下CobaltStrike之后,打算在Kali上本地搭建一个服务器端, 查看了一下Kali的JDK配置发现版本是13.X的,然而CobaltStrike最好 ...
- Spring Cloud 整合 Feign 的原理
前言 在 上篇 介绍了 Feign 的核心实现原理,在文末也提到了会再介绍其和 Spring Cloud 的整合原理,Spring 具有很强的扩展性,会把一些常用的解决方案通过 starter 的方式 ...
- 详细解释 使用FileReference类加载和保存本地文件
一般而言,用户不希望web浏览器中运行的应用程序访问电脑硬盘里的文件.然而,随着基于浏览器(browser-based)的富因特网应用程序的增多,一些应用程序迫切需要访问用户所选择的文件,或者将文件保 ...
- Gerrit GitLab GitHub的几点不同
代码评审的方式不一样 GitHub是基于Pull Request 进行代码评审; GitLab是基于Merge Request 进行代码评审; Gerrit是基于Change Request 进行代码 ...
- 微信小程序支付 后台处理逻辑 (转)
<?phpnamespace app\parent\controller; use think\Request; class Wxpay{ function wechat(){ //微信配 ...