日常开发中,会经常遇到说,前台调服务,然后触发一个比较耗时的异步服务,且不用等异步任务的处理结果就对原服务进行返回。这里就涉及的Java异步调用的一个知识。下面本文尝试将Java异步调用的多种方式进行归纳。

一、通过创建新线程


首先的我们得认识到,异步调用的本质,其实是通过开启一个新的线程来执行。如以下例子:

public static void main(String[] args) throws Exception{

    System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());

    new Thread(() -> {
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
}).start(); Thread.sleep(2000); System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis()); }

数据结果如下所示,我们知道,System.currentTimeMillis()时间单位为ms。

主线程 =====> 开始 =====> 1627893837146
异步线程 =====> 开始 =====> 1627893837200
主线程 =====> 结束 =====> 1627893839205
异步线程 =====> 结束 =====> 1627893842212

我们通过线程休眠来达成主线程执行时间2秒左右,异步线程执行5秒左右的效果。通过打印出来的时间戳倒数第四位(秒位)我们可以看出,两个的线程执行总时间为5秒左右,符合异步执行的特征

以上是采用Runable实现多线程创建方式的lambda写法,关于的lambda知识,可参考Java Lambda 表达式;而关于多线程的多种实现方式,Java多线程事务管理一文有提及,可移步查看

二、通过线程池


因为异步任务的实现本质的由新线程来执行任务,所以通过线程池的也可以实现异步执行。写法同我们利用线程池开启多线程一样。但由于我们的目的不是执行多线程,而是异步执行任务,所以一般需要另外一个线程就够了。

因此区别于执行多线程任务的我们常用的newFixedThreadPool,在执行异步任务时,我们用newSingleThreadExecutor 来创建一个单个线程的线程池。

public static void main(String[] args) throws Exception{

    System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());

    ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(()->{
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
});
executorService.shutdown(); // 回收线程池 Thread.sleep(2000); System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis()); }

执行结果如下:

主线程 =====> 开始 =====> 1627895467578
异步线程 =====> 开始 =====> 1627895467635
主线程 =====> 结束 =====> 1627895469644
异步线程 =====> 结束 =====> 1627895472649

可以看到,结果跟第一种结果是基本一致的。

温馨提示:不要忘记线程池的回收

三、通过@Async注解


我们都知道,SpringBoot项目有一个的很重要的特点就是的注解化。如果你的项目是SpringBoot,那就又多了一种选择——@Async注解。

使用起来也非常简单,将要异步执行的代码封装成一个方法,然后用@Async注解该方法,然后在主方法中直接调用就行。

@Test
public void mainThread() throws Exception{ System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());
collectionBill.asyncThread();
Thread.sleep(2000);
System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis()); Thread.sleep(4000); // 用于防止jvm停止,导致异步线程中断
}
@Async
public void asyncThread(){
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
}

执行结果如下:

主线程 =====> 开始 =====> 1627897539948
异步线程 =====> 开始 =====> 1627897539956
主线程 =====> 结束 =====> 1627897541965
异步线程 =====> 结束 =====> 1627897544966

有以下两点需要注意:

  1. 类似@Tranctional注解,@Async注解的方法与调用方法不能在同一个类中,否则不生效
  2. JUnit框架的设计不考虑多线程场景,所以主线程退出后,子线程也会跟着立即退出,所以可以在后面加多线程休眠时间来观察异步线程的执行情况

四、通过CompletableFuture


CompletableFuture是JDK1.8的新特性,是对Future的扩展。CompletableFuture实现了CompletionStage接口和Future接口,增加了异步回调、流式处理、多个Future组合处理的能力。

实现代码如下:

public static void main(String[] args) throws Exception{

    System.out.println("主线程 =====> 开始 =====> " + System.currentTimeMillis());

    ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture.runAsync(() ->{
System.out.println("异步线程 =====> 开始 =====> " + System.currentTimeMillis());
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("异步线程 =====> 结束 =====> " + System.currentTimeMillis());
},executorService);
executorService.shutdown(); // 回收线程池 Thread.sleep(2000); System.out.println("主线程 =====> 结束 =====> " + System.currentTimeMillis()); }

同样可以实现类似的结果如下:

主线程 =====> 开始 =====> 1627898354914
异步线程 =====> 开始 =====> 1627898354977
主线程 =====> 结束 =====> 1627898356980
异步线程 =====> 结束 =====> 1627898359979

CompletableFuture有者非常强大的功能,能给我们带来非常丝滑的编程体验。后续会写一篇文章来详细介绍CompletableFuture

说说Java异步调用的几种方式的更多相关文章

  1. Java 异步编程的几种方式

    前言 异步编程是让程序并发运行的一种手段.它允许多个事情同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行,当方法执行完成时通知给主线程根据需要获取其执行结果或者失 ...

  2. [OpenSource]浅谈.Net和Java互相调用的三种方式

    在很多的大型系统开发中,开发工具往往不限制于同一种开发语言,而是会使用多种开发语言的混合型开发.目前Java和.Net都声称自己占85%的市场份额,不管谁对谁错,Java和.Net是目前应用开发的两个 ...

  3. 浅谈.Net和Java互相调用的三种方式

    在很多的大型系统开发中,开发工具往往不限制于同一种开发语言,而是会使用多种开发语言的混合型开发.目前Java和.Net都声称自己占85%的市场份 额,不管谁对谁错,Java和.Net是目前应用开发的两 ...

  4. c#异步调用的几种方式

    首先,我们分析一下异步处理的环境 需要在当前线程中获取返回值 不需要在当前线程中获取返回值,但是仍然需要对返回值做处理对于第1中情况,还可以继续细分 在当前线程中启动线程T,然后继续执行当前线程中的其 ...

  5. Java中创建对象的几种方式

    Java中创建对象的五种方式: 作为java开发者,我们每天创建很多对象,但是我们通常使用依赖注入的方式管理系统,比如:Spring去创建对象,然而这里有很多创建对象的方法:使用New关键字.使用Cl ...

  6. JAVA解析XML的四种方式

    java解析xml文件四种方式 1.介绍 1)DOM(JAXP Crimson解析器) DOM是用与平台和语言无关的方式表示XML文档的官方W3C标准.DOM是以层次结构组织的节点或信息片断的集合.这 ...

  7. Java中创建对象的五种方式

    我们总是讨论没有对象就去new一个对象,创建对象的方式在我这里变成了根深蒂固的new方式创建,但是其实创建对象的方式还是有很多种的,不单单有new方式创建对象,还有使用反射机制创建对象,使用clone ...

  8. 【转】Java中创建对象的5种方式

    Java中创建对象的5种方式   作为Java开发者,我们每天创建很多对象,但我们通常使用依赖管理系统,比如Spring去创建对象.然而这里有很多创建对象的方法,我们会在这篇文章中学到. Java中有 ...

  9. Java创建线程的四种方式

    Java创建线程的四种方式 1.继承Thread类创建线程 定义Thread类的子类,并重写该类的run方法,run()方法的内容就是该线程执行的内容 创建Thread子类的实例,即创建了线程对象. ...

随机推荐

  1. StackOverflow上面 7个最好的Java答案

    StackOverflow发展到目前,已经成为了全球开发者的金矿.它能够帮助我们找到在各个领域遇到的问题的最有用的解决方案,同时我们也会从中学习到很多新的东西.这篇文章是在我们审阅了StackOver ...

  2. 强烈推荐!15 个Github 顶级Java教程类开源项目

    大家好,我是 Guide 哥!今天给大家推荐 15 个新手也能看懂的 Java 教程方向的开源项目.这些项目无论是对于你学习 Java 还是准备 Java 方向的面试都非常有帮助. 正如我第一个要推荐 ...

  3. NOIP模拟测试10「大佬·辣鸡·模板」

    大佬 显然假期望 我奇思妙想出了一个式子$f[i]=f[i-1]+\sum\limits_{j=1}^{j<=m} C_{k \times j}^{k}\times w[j]$ 然后一想不对得容 ...

  4. Tomcat与spring的类加载器案例

    Tomcat与spring的类加载器案例接下来将介绍<深入理解java虚拟机>一书中的案例,并解答它所提出的问题.(部分类容来自于书中原文) Tomcat中的类加载器在Tomcat目录结构 ...

  5. C#调用JAVA(二)调用方法

    上期我们创建了jar包并放到了unity中,那么我们继续 如果您还没有看上一期请先看上一期,这是链接 C#调用JAVA(一)制作jar包 - 执著GodShadow - 博客园 (cnblogs.co ...

  6. Kubernetes使用节点亲缘性将POD调度到特定节点上

    节点污点可以用来让pod远离特定的节点,尽量在不修改已有pod信息的前提,通过在节点添加污点信息,来拒绝pod在某些节点上的部署. 而现在介绍一种叫做节点亲缘性,通过明确的在pod中添加的信息,来决定 ...

  7. SpringBoot缓存管理(一) 默认缓存管理

    前言 Spring框架支持透明地向应用程序添加缓存对缓存进行管理,其管理缓存的核心是将缓存应用于操作数据的方法(包括增删查改等),从而减少操作数据的执行次数(主要是查询,直接从缓存中读取数据),同时不 ...

  8. 通过xrdp连接centos7桌面:

    6.1.需求场景: 1.希望使用windows远程访问centos图形界面,使用xmanager连接centos远程桌面时有以下问题: (1)只能有一个用户同时使用xmanger连接远程桌面,多个用户 ...

  9. flex mx:TabNavigator进行选项卡切换,需要进行交互时。发生Error #1009错误

    当需要进行 mx:TabNavigator选项卡进行切换时,需要进行交互,然后却报了"TypeError: Error #1009: 无法访问空对象引用的属性或方法."错误,产生这 ...

  10. PS 快速抠图

    1.选择矩形选框工具-->选择图中要抠掉的地方-->右键填充-->确定