现在的位置:主页 > 综合新闻 >

CompletableFuture:异步编程没那么难

来源:电脑编程技巧与维护 【在线投稿】 栏目:综合新闻 时间:2020-09-01

【作者】网站采编

【关键词】

【摘要】我们在日常开发中经常用多线程优化性能,其实不过就是将串行操作变成并行操作。如果仔细观察,你还会发现在串行转换成并行的过程中,一定会涉及到异步化,例如下面的示例代码

我们在日常开发中经常用多线程优化性能,其实不过就是将串行操作变成并行操作。如果仔细观察,你还会发现在串行转换成并行的过程中,一定会涉及到异步化,例如下面的示例代码,现在是串行的,为了提升性能,我们得把它们并行化,那具体实施起来该怎么做呢?

//以下两个方法都是耗时操作doBizA();doBizB();

还是挺简单的,就像下面代码中这样,创建两个子线程去执行就可以了。你会发现下面的并行方案,主线程无需等待 doBizA() 和 doBizB() 的执行结果,也就是说 doBizA() 和 doBizB() 两个操作已经被异步化了。

new?Thread(()->doBizA())??.start();new?Thread(()->doBizB())??.start();??

异步化,是并行方案得以实施的基础,更深入地讲其实就是:利用多线程优化性能这个核心方案得以实施的基础。看到这里,相信你应该就能理解异步编程最近几年为什么会大火了,因为优化性能是互联网大厂的一个核心需求啊。Java 在 1.8 版本提供了 CompletableFuture 来支持异步编程,CompletableFuture 有可能是你见过的最复杂的工具类了,不过功能也着实让人感到震撼。

CompletableFuture 的核心优势

为了领略 CompletableFuture 异步编程的优势,这里我们用 CompletableFuture 重新实现前面曾提及的烧水泡茶程序。首先还是需要先完成分工方案,在下面的程序中,我们分了 3 个任务:

  • 任务 1 负责洗水壶、烧开水
  • 任务 2 负责洗茶壶、洗茶杯和拿茶叶
  • 任务 3 负责泡茶。

其中任务 3 要等待任务 1 和任务 2 都完成后才能开始。

这个分工如下图所示。

下面是代码实现,你先略过 runAsync()、supplyAsync()、thenCombine() 这些不太熟悉的方法,从大局上看,你会发现:

  1. 无需手工维护线程,没有繁琐的手工维护线程的工作,给任务分配线程的工作也不需要我们关注;
  2. 语义更清晰,例如 f3 = (f2, ()->{}) 能够清晰地表述“任务 3 要等待任务 1 和任务 2 都完成后才能开始”;
  3. 代码更简练并且专注于业务逻辑,几乎所有代码都是业务逻辑相关的。
//任务1:洗水壶->烧开水CompletableFuture?f1?=???(()->{??(\"T1:洗水壶...\");??sleep(1,?);??(\"T1:烧开水...\");??sleep(15,?);});//任务2:洗茶壶->洗茶杯->拿茶叶CompletableFuture?f2?=???(()->{??(\"T2:洗茶壶...\");??sleep(1,?);??(\"T2:洗茶杯...\");??sleep(2,?);??(\"T2:拿茶叶...\");??sleep(1,?);??return?\"龙井\";});//任务3:任务1和任务2完成后执行:泡茶CompletableFuture?f3?=???(f2,?(__,?tf)->{????(\"T1:拿到茶叶:\"?+?tf);????(\"T1:泡茶...\");????return?\"上茶:\"?+?tf;??});//等待任务3执行结果(f3.join());void?sleep(int?t,?TimeUnit?u)?{??try?{????u.sleep(t);??}catch(InterruptedException?e){}}//?一次执行结果:T1:洗水壶...T2:洗茶壶...T1:烧开水...T2:洗茶杯...T2:拿茶叶...T1:拿到茶叶:龙井T1:泡茶...上茶:龙井

领略 CompletableFuture 异步编程的优势之后,下面我们详细介绍 CompletableFuture 的使用,首先是如何创建 CompletableFuture 对象。

创建 CompletableFuture 对象

创建 CompletableFuture 对象主要靠下面代码中展示的这 4 个静态方法,我们先看前两个。在烧水泡茶的例子中,我们已经使用了runAsync(Runnable runnable)supplyAsync(Supplier supplier),它们之间的区别是:Runnable 接口的 run() 方法没有返回值,而 Supplier 接口的 get() 方法是有返回值的。

前两个方法和后两个方法的区别在于:后两个方法可以指定线程池参数。

默认情况下 CompletableFuture 会使用公共的 ForkJoinPool 线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:- 来设置 ForkJoinPool 线程池的线程数)。如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

文章来源:《电脑编程技巧与维护》 网址: http://www.dnbcjqywh.cn/zonghexinwen/2020/0901/432.html

上一篇:3分钟短文:Laravel 编程中优雅地添加定义常量
下一篇:编程语言拟人:来自C++、Python、C语言的“傲娇”

电脑编程技巧与维护投稿 | 电脑编程技巧与维护编辑部| 电脑编程技巧与维护版面费 | 电脑编程技巧与维护论文发表 | 电脑编程技巧与维护最新目录
Copyright © 2018 《电脑编程技巧与维护》杂志社 版权所有
投稿电话: 投稿邮箱: