CompletableFuture:组合式异步编程
响应 CompletableFuture 的 completion 事件
付诸实践
现在通过randomDelay方法模拟远程方法调用,产生一个介于0.5秒到2.5秒的随机延迟,不再使用恒定1秒的延迟值。下面的代码清单应用了这一改变,执行这段代码你会看到不同商店的价格不再像之前那样总是在一个时刻返回,而是随着商店折扣价格返回的顺序逐一地打印输出。为了让这一改变的效果更加明显,我们对代码进行了微调,在输出中打印每个价格计算所消耗的时间:
long start = System.nanoTime();
CompletableFuture[] futures = findPricesStream("myPhone27S")
.map(f -> f.thenAccept(
s -> System.out.println(s + " (done in " + ((System.nanoTime() - start) / 1_000_000) + " msecs)")))
.toArray(size -> new CompletableFuture[size]);
CompletableFuture.allOf(futures).join();
System.out.println("All shops have now responded in "
+ ((System.nanoTime() - start) / 1_000_000) + " msecs");
运行这段代码所产生的输出如下:
BuyItAll price is 184.74 (done in 2005 msecs)
MyFavoriteShop price is 192.72 (done in 2157 msecs)
LetsSaveBig price is 135.58 (done in 3301 msecs)
ShopEasy price is 167.28 (done in 3869 msecs)
BestPrice price is 110.93 (done in 4188 msecs)
All shops have now responded in 4188 msecs
由于随机延迟的效果,第一次价格查询比最慢的查询要快两倍多。
小结
这部分的主要内容如下:
- 执行比较耗时的操作时,尤其是那些依赖一个或多个远程服务的操作,使用异步任务可以改善程序的性能,加快程序的响应速度。
- 应该尽可能地为客户提供异步
API
。使用CompletableFuture
类提供的特性,能够轻松地实现这一目标。 CompletableFuture
类还提供了异常管理的机制,让你有机会抛出/管理异步任务执行中发生的异常。- 将同步
API
的调用封装到一个CompletableFuture
中,你能够以异步的方式使用其结果。 - 如果异步任务之间相互独立,或者它们之间某一些的结果是另一些的输入,你可以将这异步任务构造或者合并成一个。
- 你可以为
CompletableFuture
注册一个回调函数,在Future
执行完毕或者它们计算的结果可用时,针对性地执行一些程序。 - 你可以决定在什么时候结束程序的运行,是等待由
CompletableFuture
对象构成的列表中所有的对象都执行完毕,还是只要其中任何一个首先完成就中止程序的运行。
新的日期和时间AP
Java
的API
提供了很多有用的组件,能帮助你构建复杂的应用。不过,Java API
也不总是完美的。大多数有经验的程序员都会赞同Java 8
之前的库对日期和时间的支持就非常不理想。然而,也不用太担心:Java 8
中引入全新的日期和时间API
就是要解决这一问题。
在Java 1.0
中,对日期和时间的支持只能依赖java.util.Date
类。正如类名所表达的,这个类无法表示日期,只能以毫秒的精度表示时间。更糟糕的是它的易用性,由于某些原因未知的设计决策,这个类的易用性被深深地损害了,比如:年份的起始选择是1900年,月份的起始从0开始。这意味着,如果你想要用Date
表示Java 8
的发布日期,即2014年3月18日,需要创建下面这样的Date
实例:
Date date = new Date(114, 2, 18);
它的打印输出效果为:
Tue Mar 18 00:00:00 CET 2014
看起来不那么直观。此外,甚至Date
类的toString
方法返回的字符串也容易误导人。以我们的例子而言,它的返回值中甚至还包含了JVM
的默认时区CET
,即中欧时间(Central Europe Time)。但这并不表示Date
类在任何方面支持时区。
随着Java 1.0
退出历史舞台,Date
类的种种问题和限制几乎一扫而光,但很明显,这些历史旧账如果不牺牲前向兼容性是无法解决的。所以,在Java 1.1
中,Date
类中的很多方法被废弃了,取而代之的是java.util.Calendar
类。很不幸,Calendar
类也有类似的问题和设计缺陷,导致使用这些方法写出的代码非常容易出错。比如,月份依旧是从0开始计算(不过,至少Calendar类拿掉了由1900年开始计算年份这一设计)。更糟的是,同时存在Date
和Calendar
这两个类,也增加了程序员的困惑。到底该使用哪一个类呢?此外,有的特性只在某一个类有提供,比如用于以语言无关方式格式化和解析日期或时间的DateFormat
方法就只在Date
类里有。
DateFormat
方法也有它自己的问题。比如,它不是线程安全的。这意味着两个线程如果尝试使用同一个formatter
解析日期,你可能会得到无法预期的结果。
所有这些缺陷和不一致导致用户们转投第三方的日期和时间库,比如Joda-Time
。为了解决这些问题,Oracle
决定在原生的Java API
中提供高质量的日期和时间支持。所以,你会看到Java
8在java.time
包中整合了很多Joda-Time
的特性。
下面会探索新的日期和时间API
所提供的新特性。首先从最基本的用例入手,比如创建同时适合人与机器的日期和时间,逐渐转入到日期和时间API
更高级的一些应用,比如操纵、解析、打印输出日期时间对象,使用不同的时区和年历。