平常开发中,会常常遇到说,前台调服务,而后触发一个比较耗时的异步服务,且不用等异步任务的处理结果就对原服务进行返回。这里就涉及的Java异步调用的一个知识。下面本文尝试将Java异步调用的多种方式进行概括。html
首先的咱们得认识到,异步调用的本质,实际上是经过开启一个新的线程来执行。如如下例子:编程
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。markdown
主线程 =====> 开始 =====> 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()); }
执行结果以下:jvm
主线程 =====> 开始 =====> 1627895467578 异步线程 =====> 开始 =====> 1627895467635 主线程 =====> 结束 =====> 1627895469644 异步线程 =====> 结束 =====> 1627895472649
能够看到,结果跟第一种结果是基本一致的。async
舒适提示:不要忘记线程池的回收ide
咱们都知道,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
有如下两点须要注意:
- 相似
@Tranctional
注解,@Async
注解的方法与调用方法不能在同一个类中,不然不生效- JUnit框架的设计不考虑多线程场景,因此主线程退出后,子线程也会跟着当即退出,因此能够在后面加多线程休眠时间来观察异步线程的执行状况
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基础]
标签: [多线程]