【Java多线程】CompletableFuture 异步多线程

1. 回顾 Future

一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。

JDK5新增了Future接口,用于描述一个异步计算的结果。

虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,我们必须使用Future.get()的方式阻塞调用线程,或者使用轮询方式判断 Future.isDone 任务是否结束,再获取结果。

这两种处理方式都不是很优雅,相关代码如下:

@Test  
public void testFuture() throws ExecutionException, InterruptedException {  ExecutorService executorService = Executors.newFixedThreadPool(5);  Future<String> future = executorService.submit(() -> {  Thread.sleep(2000);  return "hello";  });  System.out.println(future.get());  System.out.println("end");  
}  

与此同时,Future无法解决多个异步任务需要相互依赖的场景,简单点说就是,主线程需要等待子线程任务执行完毕之后在进行执行,这个时候你可能想到了CountDownLatch,没错确实可以解决,代码如下。

这里定义两个Future,第一个通过用户id获取用户信息,第二个通过商品id获取商品信息。

@Test  public void testCountDownLatch() throws InterruptedException, ExecutionException {  ExecutorService executorService = Executors.newFixedThreadPool(5);  CountDownLatch downLatch = new CountDownLatch(2);  long startTime = System.currentTimeMillis();  Future<String> userFuture = executorService.submit(() -> {  //模拟查询商品耗时500毫秒  Thread.sleep(500);  downLatch.countDown();  return "用户A";  });  Future<String> goodsFuture = executorService.submit(() -> {  //模拟查询商品耗时500毫秒  Thread.sleep(400);  downLatch.countDown();  return "商品A";  });  downLatch.await();  //模拟主程序耗时时间  Thread.sleep(600);  System.out.println("获取用户信息:" + userFuture.get());  System.out.println("获取商品信息:" + goodsFuture.get());  System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");  }  

「运行结果」

获取用户信息:用户A
获取商品信息:商品A
总共用时1110ms
从运行结果可以看出结果都已经获取,而且如果我们不用异步操作,执行时间应该是:500+400+600 = 1500,用异步操作后实际只用1110。

但是Java8以后这就不在认为是一种优雅的解决方式,接下来了解下CompletableFuture的使用。

2. CompletableFuture

JDK8之后,提供了CompletableFuture实现异步线程。不推荐再使用Future

通过CompletableFuture实现上面示例

@Test  
public void testCompletableInfo() throws InterruptedException, ExecutionException {  long startTime = System.currentTimeMillis();  //调用用户服务获取用户基本信息  CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() ->  //模拟查询商品耗时500毫秒  {  try {  Thread.sleep(500);  } catch (InterruptedException e) {  e.printStackTrace();  }  return "用户A";  });  //调用商品服务获取商品基本信息  CompletableFuture<String> goodsFuture = CompletableFuture.supplyAsync(() ->  //模拟查询商品耗时500毫秒  {  try {  Thread.sleep(400);  } catch (InterruptedException e) {  e.printStackTrace();  }  return "商品A";  });  System.out.println("获取用户信息:" + userFuture.get());  System.out.println("获取商品信息:" + goodsFuture.get());  //模拟主程序耗时时间  Thread.sleep(600);  System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");  
}  

[运行结果]

获取用户信息:用户A
获取商品信息:商品A
总共用时1112ms

通过CompletableFuture可以很轻松的实现CountDownLatch的功能
当然,CompletableFuture还有其他许多新功能:比如可以实现:任务1执行完了再执行任务2,甚至任务1执行的结果,作为任务2的入参数等等强大功能,下面就来学学CompletableFuture的API。

CompletableFuture创建方式

1、常用的4种创建方式

CompletableFuture源码中有四个静态方法用来执行异步任务

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier){..}  
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor){..}  
public static CompletableFuture<Void> runAsync(Runnable runnable){..}  
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor){..}  

一般我们用上面的静态方法来创建CompletableFuture
区别:
supplyAsync执行任务,支持返回值。
runAsync执行任务,没有返回值。
supplyAsync方法

//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务  
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)  
//自定义线程,根据supplier构建执行任务  
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)  
「runAsync方法」//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务  
public static CompletableFuture<Void> runAsync(Runnable runnable)   
//自定义线程,根据runnable构建执行任务  
public static CompletableFuture<Void> runAsync(Runnable runnable,  Executor executor)  

2、结果获取的4种方式

对于结果的获取CompltableFuture类提供了四种方式

//方式一  
public T get()  
//方式二  
public T get(long timeout, TimeUnit unit)  
//方式三  
public T getNow(T valueIfAbsent)  
//方式四  
public T join()  

说明:

get()get(long timeout, TimeUnit unit) => 在Future中就已经提供了,后者提供超时处理,如果在指定时间内未获取结果将抛出超时异常
getNow() => 立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,如果未计算完成将返回设定的valueIfAbsent值
join() => 方法里不会抛出异常
示例:

@Test  
public void testCompletableGet() throws InterruptedException, ExecutionException {  CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  return "商品A";  });  // getNow方法测试   System.out.println(cp1.getNow("商品B"));  //join方法测试   CompletableFuture<Integer> cp2 = CompletableFuture.supplyAsync((() -> 1 / 0));  System.out.println(cp2.join());  System.out.println("-----------------------------------------------------");  //get方法测试  CompletableFuture<Integer> cp3 = CompletableFuture.supplyAsync((() -> 1 / 0));  System.out.println(cp3.get());  
}  

「运行结果」

第一个执行结果为 「商品B」,因为要先睡上1秒结果不能立即获取
join方法获取结果方法里不会抛异常,但是执行结果会抛异常,抛出的异常为CompletionException
get方法获取结果方法里将抛出异常,执行结果抛出的异常为ExecutionException

3. CountDownLatch

CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。

CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。

CountDownLatch 常用方法说明

CountDownLatch(int count); //构造方法,创建一个值为count 的计数器。await();//阻塞当前线程,将当前线程加入阻塞队列。await(long timeout, TimeUnit unit);//在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行,countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。

举例:用CountDownLatch 来优化我们的报表统计

运营系统有统计报表、业务为统计每日的用户新增数量、订单数量、商品的总销量、总销售额…等多项指标统一展示出来,因为数据量比较大,统计指标涉及到的业务范围也比较多,所以这个统计报表的页面一直加载很慢,所以需要对统计报表这块性能需进行优化。

问题分析:
统计报表页面涉及到的统计指标数据比较多,每个指标需要单独的去查询统计数据库数据,单个指标只要几秒钟,但是页面的指标有10多个,所以整体下来页面渲染需要将近一分钟。

解决方案:
任务时间长是因为统计指标多,而且指标是串行的方式去进行统计的,我们只需要考虑把这些指标从串行化的执行方式改成并行的执行方式,那么整个页面的时间的渲染时间就会大大的缩短, 如何让多个线程同步的执行任务,我们这里考虑使用多线程,每个查询任务单独创建一个线程去执行,这样每个统计指标就可以并行的处理了。

要求:
因为主线程需要每个线程的统计结果进行聚合,然后返回给前端渲染,所以这里需要提供一种机制让主线程等所有的子线程都执行完之后再对每个线程统计的指标进行聚合。 这里我们使用CountDownLatch 来完成此功能。

模拟代码

1、分别统计4个指标用户新增数量、订单数量、商品的总销量、总销售额;
2、假设每个指标执行时间为3秒。如果是串行化的统计方式那么总执行时间会为12秒。
3、我们这里使用多线程并行,开启4个子线程分别进行统计
4、主线程等待4个子线程都执行完毕之后,返回结果给前端。

//用于聚合所有的统计指标private static Map map=new HashMap();//创建计数器,这里需要统计4个指标private static CountDownLatch countDownLatch=new CountDownLatch(4);public static void main(String[] args) {//记录开始时间long startTime=System.currentTimeMillis();Thread countUserThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在统计新增用户数量");Thread.sleep(3000);//任务执行需要3秒map.put("userNumber",1);//保存结果值//countDownLatch.countDown();//标记已经完成一个任务System.out.println("统计新增用户数量完毕");} catch (InterruptedException e) {e.printStackTrace();} finally{countDownLatch.countDown();//标记已经完成一个任务}}});Thread countOrderThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在统计订单数量");Thread.sleep(3000);//任务执行需要3秒map.put("countOrder",2);//保存结果值System.out.println("统计订单数量完毕");} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();//标记已经完成一个任务}}});Thread countGoodsThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在商品销量");Thread.sleep(3000);//任务执行需要3秒map.put("countGoods",3);//保存结果值System.out.println("统计商品销量完毕");} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();//标记已经完成一个任务}}});Thread countmoneyThread=new Thread(new Runnable() {public void run() {try {System.out.println("正在总销售额");Thread.sleep(3000);//任务执行需要3秒map.put("countmoney",4);//保存结果值System.out.println("统计销售额完毕");} catch (InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();//标记已经完成一个任务}}});//启动子线程执行任务countUserThread.start();countGoodsThread.start();countOrderThread.start();countmoneyThread.start();try {//主线程等待所有统计指标执行完毕countDownLatch.await();long endTime=System.currentTimeMillis();//记录结束时间System.out.println("------统计指标全部完成--------");System.out.println("统计结果为:"+map.toString());System.out.println("任务总执行时间为"+(endTime-startTime)/1000+"秒");} catch (InterruptedException e) {e.printStackTrace();}}

注意:countDownLatch.countDown();放在finally中,防止线程异常把机器卡死

「运行结果」
在这里插入图片描述

参考链接
https://zhuanlan.zhihu.com/p/647743286
https://zhuanlan.zhihu.com/p/95835099?utm_id=0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1380280.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Kotlin 基础教程一

Kotlin 基本数据类型 Java | Kotlin byte Byte short Short int Int long Long float Float double Double boolean Boolean c…

php怎么实现拼图功能,照片拼图效果怎么做 将一张照片制作成拼图的效果

最近的电影少年的你上映几天了&#xff0c;该电影是校园欺凌题材&#xff0c;听说剧情的深度可以给观众带来沉思&#xff0c;看到消息说今天这部电影也是突破六亿票房&#xff0c;也是厉害的了&#xff0c;找个时间真想去看看呢&#xff01;好啦&#xff0c;回归主题&#xff0…

Gradio:交互式Python数据应用程序的新前沿

一、说明 什么是Gradio以及如何使用Gradio在Python中创建DataApp或Web界面&#xff1f;使用 Gradio 将您的 Python 数据科学项目转换为交互式应用程序。 摄影&#xff1a;Elijah Merrell on Unsplash Gradio是一个Python库&#xff0c;允许我们快速为机器学习模型创建可定制的接…

controller层,service层,mapper层,entity层的作用与联系。

一. controller层 controller层是用来接受前台数据和返回页面请求信息的&#xff0c;Controller层是不允许直接操作数据库的&#xff01;它就像一个服务员&#xff0c;哪桌客人需要点菜了&#xff0c;就喊一声服务员&#xff01; 对应的&#xff0c;外界需要完成什么样的业务&…

“一日之际在于晨”,欢迎莅临WAVE SUMMIT上午场:Arm 虚拟硬件早餐交流会

8月16日&#xff0c;盛夏的北京将迎来第九届WAVE SUMMIT深度学习开发者大会。在峰会主论坛正式开启前&#xff0c;让我们先用一份精美的元气早餐&#xff0c;和一场“Arm虚拟硬件交流会”&#xff0c;唤醒各位开发小伙伴的开发魂&#xff01; 8月16日&#xff0c;WAVE SUMMIT大…

雷军爆料骁龙855新机 液冷3.0+跑车造型 媲美iPhone

目前各大手机厂商都在着手发布骁龙855新机&#xff0c;个别厂商已经抢先发布了&#xff0c;最受国内用户关注的自然还是小米9了&#xff0c;不过近日&#xff0c;雷军在个人微博爆料&#xff0c;又一款骁龙855新机将会在下周一&#xff0c;也就是3月18日发布剧情介绍。 这款手机…

互联网专家资源分享(二)

1. 换位思考 沈阳 前台页面处理&#xff0c;利用CSS和DIV构建页面。面向在校毕业生或者刚参加工作人员 divcss页面处理html控件解析&#xff0c;数据处理&#xff0c;sql语句构造 2. moosefs调优 济南 tcp/ip&#xff0c;精通tcp/ip协议。 精通linux下的tcp/ip开发。&#…

WEBSHELL 提权方法总结

最全的 WEBSHELL 提权方法总结 来到的请留个脚印&#xff0c;谢谢 在得到了一个 Webshell 之后&#xff0c;如果能进一步利用系统的配置不当取得更高的权限&#xff0c;一直是广大黑友们所津 津乐道的话题&#xff0c;也是高手和菜鸟间最大的区别。本文将从一个大角度总括当前…

打破智能手机消费格局的将不是另一台智能手机,而是游戏

文|佘凯文 来源|智能相对论&#xff08;aixdlun&#xff09; 云游戏的概念&#xff0c;在游戏圈越来越热。从微软的Project xCloud的云游戏平台、到谷歌的Project Stream云游戏服务、再到索尼基于PS4的云游戏服务以及腾讯也宣布与英特尔联手推出云游戏平台“腾讯即玩”&#…

游戏市场阴影下的游戏手机厂商,和他们无法触碰的未来

出于以版号为主的一系列问题&#xff0c;中国手游市场遇冷是近一年以来的显著现象。根据App Annie的调查显示&#xff0c;在过去两年里&#xff0c;全球App的用户总使用时长增长了50%&#xff0c;而游戏所占比重一直稳步上升&#xff0c;但中国市场的游戏用户却支出锐减&#x…

雷军亲自捧场,双液冷游戏手机演绎“暴力美学”

10月23日&#xff0c;黑鲨科技最新旗舰黑鲨游戏手机Helo在北京惊艳上市。黑鲨科技创始人兼CEO吴世敏&#xff0c;小米集团创始人、董事长兼首席执行官雷军&#xff0c;中国文化娱乐行业协会会长刘金华、京东商城通讯事业部总裁陈婷、南昌金开集团董事长胡顺明以及优点科技创始人…

雷军亲自站台,游戏手机能否成为市场增长新引擎?

从2017年开始&#xff0c;智能手机市场的表现便让不少人大跌眼镜。 Canalys发布的统计报告显示&#xff0c;2017年中国智能手机市场年总出货量为4.59亿部&#xff0c;较2016年下跌4%&#xff0c;这也是中国首次出现智能手机年总出货量下滑的情况。 而近日中国信息通信研究院发布…

黑鲨的“游戏”人生:寻找手机红海之中一抹蓝

手机厂商的日子并不好过。 据第三方机构发布数据&#xff0c;2019年上半年&#xff0c;国内手机市场出货量仅为1.86亿部&#xff0c;同比下降5.1%&#xff0c;增量时代已过&#xff0c;行业进入存量时代&#xff0c;用户逐渐向头部企业集中。被挤到墙角的手机厂商&#xff0c;…

神仙打架!鲁大师发布2021半年报,黑鲨游戏手机4 Pro夺冠!

随着鲁大师2021年半年报发布&#xff0c;黑鲨游戏手机4 Pro的性能也被大家清晰的了解到。在本次鲁大师2021年半年报中&#xff0c;黑鲨游戏手机4 Pro夺得了手机综合性能榜的冠军宝座&#xff0c;成为名副其实的性能之王&#xff01; 黑鲨游戏手机4 Pro作为主打游戏性能的旗舰机…

【数据结构与算法】稀疏数组

文章目录 一&#xff1a;为什么会使用稀疏数组1.1 先看一个实际的需求1.2 基本介绍1.2.1 稀疏数组的处理方法1.2.2 数组的举例说明1.2.3 应用实例1.2.4 整体思路分析二维数组转稀疏数组的思路稀疏数组转原始的二维数组的思路 二&#xff1a;代码实现2.1 创建一个原始的11*11二维…

一文盘点 Zebec 生态的几个利好预期

Zebec Protocol 是目前商业进展最快的流支付体系&#xff0c;也是推动流支付向 Web2 世界发展的主要生态。目前&#xff0c;其已经与包括 Visa、Master 等支付巨头展开了合作&#xff0c;以推出银行卡的方式进一步向金融发达地区推出 Zebec Card 以拓展业务&#xff0c;前不久其…

人大金仓三大兼容:Oracle迁移无忧

企业级应用早期的架构模式是C/S&#xff08;Client/Server&#xff09;模式&#xff0c;Client做人机交互逻辑的呈现&#xff0c;Sever做业务计算逻辑的实现。这就类似餐馆的运作模式&#xff0c;Client是前台的服务员提供点菜和上菜服务&#xff0c;而Server则是后厨完成菜品的…

windows7专业版_windows7专业版和旗舰版的区别

&#xff37;indows7专业版和旗舰版有什么区别&#xff0c;二者有什么不同&#xff0c;相信有很多小伙伴还是不太了解的&#xff0c;下面就来为大家解答一下&#xff37;indows7专业版和旗舰版的区别&#xff1a; &#xff37;indows7专业版和旗舰版的区别 1、Windows7专业版&a…

【学习日记】【FreeRTOS】手动任务切换详解

前言 本文是关于 FreeRTOS 中实现两个任务轮流切换并执行的代码详解。目前不支持优先级&#xff0c;仅实现两个任务轮流切换。 一、任务的自传 任务从生到死的过程究竟是怎么样的呢&#xff1f;&#xff08;其实也没死&#xff09;&#xff0c;这个问题一直困扰着我&#xf…

服务器系统2012r2升级专业版,Windows Server 2012 R2版本区别

慕工程0101907 Windows Server 2012 R2是最新的服务器版本Windows&#xff0c;于2013年10月18日发布。这是Windows 8.1的服务器版本&#xff0c;在2013年6月3日的TechEd北美公布。Windows Server 2012&#xff0c;Datacenter和Standard版功能相同&#xff0c;变化只有授权&…