CompletableFuture是Java8新增的一个超大型工具类,为什么说她大呢?因为一方面它实现了Future接口,更重要的是,它实现了CompletionStage接口.这个接口也是Java8新增加的,而CompletionStage拥有多达约40种方法,
通过CompletableFuture提供进一步封装,我们很容易实现Future模式那样的异步调用,例如:
上述代码中CompletableFuture.supplyAsync()方法构造了一个CompletableFuture实例,在supplyAsync()函数中,他会在一个新的线程中,执行传入的参数.在这里,他会执行calc()方法,而calc()方法的执行可能是比较慢的,但是不影响CompletableFuture实例的构造速度,因此supplyAsync()会立即返回,他返回的CompletableFuture对象实例就可以作为这次调用的契约,在将来任何场合,用于获得最终的计算结果.在CompletableFuture中,类似的工厂方法有以下几个:
其中supplyAsync()方法用于那些需要返回值的场景,比如计算某个数据,而runAsync()方法用于没有返回值的场景,比如,仅仅是简单地执行某一个异步动作.
首先说明一下已Async结尾的方法都是可以异步执行的,如果指定了线程池,会在指定的线程池中执行,如果没有指定,默认会在ForkJoinPool.commonPool()中执行
在这两对方法中,都有一个方法可以接手一个Executor参数,这使我们可以让Supplier或者Runnable在指定的线程池工作,如果不指定,则在默认的系统公共的ForkJoinPool.common线程池中执行.
流式调用
在前文中我已经简单的提到CompletionStage的约40个接口为函数式编程做准备的,在这里,就让我们看一下,如果使用这些接口进行函数式的流式API调用
上述代码中,使用supplyAsync()函数执行了一个异步任务,接着连续使用流式调用对任务处理结果进行在加工,直到最后的结果输出:
CompletableFuture中的异常处理
组合多个CompletableFuture
CompletableFuture 还允许你将多个CompletableFuture进行组合,一种方法是使用thenCompose(),它的方法签名如下:
另外一种组和多个CompletableFuture的方法是thenCombine()它的签名如下:
实现异步API
将同步方法装换为异步方法
错误处理
上述代码,如果没有意外,可以正常工作,但是如果价格计算过程中生产了错误会怎样呢?非常不幸,这种情况下你会得到一个相当糟糕的结果:
用于提示错误的异常会限制在视图计算商品的价格的当前线程的范围内,最终会杀死该线程,而这会导致等待get方法放回结果的客户端永久的被阻塞,而这会导致等待get方法放回结果的客户端永久的被阻塞, 为了让客户端能了解商店无法提供请求商品价格的原因.我们对代码优化,!
使用工厂方法supplyAsync创建CompletableFuture
让代码免受阻塞之苦
使用平行流对请求进行并行操作
相当不错,看起来这是个简单有效的主意,对4个不同商店的查询实现了并行.所有完成操作的总耗时只有1秒多一点,让我们尝试使用CompletableFuture,将findprices 方法中对不同商店的同步调用替换为异步调用.
使用CompletableFuture发起异步请求
结果让我们失望了.我们采用异步调用新版方法,和并行差不多
寻找更好的方案
经过我增加商店数量,然后使用三种方式反复的测试,发现了一个问题,并行流和异步调用的性能不分伯仲,究其原因都一样,它们内部采用的是同样的通用线程池,默认都使用固定数目的线程,具体线程数取决于
Runtime.getRuntime.availableProcessors()反回值,然而,CompletableFuture具有一定的优势,因为它允许你对执行器进行配置,尤其是线程池的大小,让它以适合应用需求的方式进行配置,满足程序的要求,而这是并行流API无法提供的.经过测试处理5个商店 是1秒多,处理9个商店也是1秒多
并行–使用流还是CompletableFutures?
目前为止,我们已经知道对集合进行并行计算有两种方式,要么将其转化为并行流,利用map这样的操作开展工作,要么枚举出集合中的每一个元素,创建新的线程,在CompletableFuture内对其进行操作,后者提供了更多的灵活性,你可以调整线程池大小,二者能帮助你确保整体计算机不会因为线程都在等待I/O而发生阻塞 我们使用这些API的建议如下:
- 如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的
- 反之,如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待).那么使用CompletableFuture是灵活性更好,你可以像前面讨论的那样,依据等待/计算,或者W/C的比率设定需要使用的线程数,
閱讀更多 澤澤vlog 的文章