比较Java 8,RxJava,Reactor
翻译 原文来自:http://alexsderkach.io/comparing-java-8-rxjava-reactor/
人们经常问我:
如果我可以使用Streams,CompletableFutures或Optionals,我为什么要使用RxJava或Reactor呢?
问题是,大多数时候你正在解决简单的任务,而你真的不需要那些库。但是,当事情变得更复杂时,你必须写一些怪异的代码。然后这段代码变得越来越复杂,难以维护。 RxJava和Reactor具有许多方便的功能,可满足您未来多年的需求。让我们定义8个标准,这将有助于我们理解标准特性与这些库之间的区别:
- Composable
- Lazy
- Reusable
- Asynchronous
- Cacheable
- Push or Pull
- Backpressure
- Operator fusion
然后定义我们将要比较的类:
- CompletableFuture
- Stream
- Optional
- Observable (RxJava 1)
- Observable (RxJava 2)
- Flowable (RxJava 2)
- Flux (Reactor Core)
Composable
所有这些类都是可组合的,允许我们在功能上进行思考。 这就是我们爱他们的原因。
CompletableFuture - 很多 .then *()方法允许构建一个管道,它可以在阶段之间传递任何内容或单个值+ throwable。
Stream - 许多可链接的运算符,允许转换输入。 可以从一个阶段传递N个值。
Optional - 一些中间操作: .map()
, .flatMap()
, .filter()
.
Observable, Flowable, Flux - 和 Stream 一样
Lazy
CompletableFuture - 不是懒惰,因为它只是异步结果的持有者。 创建这些对象以表示已经启动的工作。 它对工作一无所知,但结果却是如此。 因此,没有办法上游并从上到下开始执行管道。 当结果设置为CompletableFuture时,将执行下一阶段。
Stream -所有中间操作都是懒惰的。 所有终端操作,触发计算。
Optional - 不是懒惰,所有操作都会立即发生。
Observable, Flowable, Flux - 在有订户之前没有任何反应。
Reusable
CompletableFuture - 可以重复使用,因为它只是一个值的包装器。 但要小心,因为这个包装器是可变的。 如果你确定没有人会在上面调用.obtrude *(),那就安全了。
Stream - 不可重复使用。 正如Java Doc所述:
应该只对一个流进行操作(调用中间或终端流操作).如果流实现检测到正在重用流,则它可能会抛出IllegalStateException。 但是,由于某些流操作可能返回其接收器而不是新的流对象,因此可能无法在所有情况下检测重用。
Optional - 完全可重复使用,因为它是不可改变的,所有的工作都是急切的。
Observable, Flowable, Flux - 可重复使用的设计。 当有订户时,所有阶段从初始点开始执行。
Asynchronous
CompletableFuture - 这个类的重点是异步链接工作。 CompletableFuture表示与某些Executor关联的作品。 如果在创建任务时未明确指定执行程序,则使用常见的ForkJoinPool。 这个池可以通过ForkJoinPool.commonPool()获得,并且默认情况下它会创建与您的系统具有的许多硬件线程一样多的线程(通常是核心数,如果您的核支持超线程,则加倍)。 但是,您可以使用JVM选项设置此池中的线程数:-Djava.util.concurrent.ForkJoinPool.common.parallelism =? 或者在每次创建工作阶段时提供自定义Executor。
Stream - 无法创建异步处理,但可以通过创建并行流来并行计算 - stream.parallel()。
Optional - 不,它只是一个容器。
Observable, Flowable, Flux - 目标是构建异步系统,但默认情况下是同步的。 subscribeOn和observeOn允许您控制订阅的调用和通知的接收(什么线程将在您的观察者上调用onNext / onError / onCompleted)。
使用subscribeOn,您可以决定执行Observable.create的Scheduler。 即使你不是自己打电话给创造,也有内在的等价物。 例:
| |
Outputs:
Reading file on thread: RxIoScheduler-2
Map on thread: RxIoScheduler-2
Result on thread: RxIoScheduler-2
相反,observeOn()控制哪个Scheduler用于调用在observeOn()之后发生的下游阶段。 例:
| |
输出:
Reading file on thread: RxIoScheduler-2
Map on thread: RxComputationScheduler-1
Result on thread: RxComputationScheduler-1
Cacheable
可重用和可缓存之间有什么区别? 假设我们有管道A,并重复使用它两次来创建管道B = A + ? 和
C = A + ?
- 如果B&C成功完成,那么类可以重复使用。
如果B&C成功完成并且管道A的每个阶段仅被调用一次,那么类是可缓存的。 要可缓存,类必须是可重用的。
CompletableFuture - 与可重用性相同的答案。
Stream - 除非调用终端操作,否则无法缓存中间结果。
Optional -'可缓存',因为所有工作都急切地发生。
Observable, Flowable, Flux - 默认情况下不缓存。 但您可以通过调用.cache()将A转换为缓存。
| |
输出:
Doing some work
10
Doing some work
20
执行 cache()
:
| |
输出:
Doing some work
10
20
Push(异步方式) or Pull(同步方式)
Stream & Optional are pull based. 您可以通过调用不同的方法(.get(),. collect()等)从管道中提取结果。 拉通常与阻塞,同步相关,这是公平的。 您调用方法并且线程开始等待数据到达。 线程被阻止直到到达。
CompletableFuture, Observable, Flowable, Flux are push based. 您订阅了管道,当有东西需要处理时,您会收到通知。 推送通常与非阻塞,异步相关联。 在某个线程中执行管道时,您可以执行任何操作。 您已经描述了要执行的代码,并且通知将在下一阶段触发此代码的执行。
Backpressure (背压)
为了产生背压,管道必须以推进为基础。
Backpressure描述了管道中的情况,当某些异步阶段无法足够快地处理这些值时,需要一种方法来告诉上游生产阶段减速。 阶段失败是不可接受的,因为数据太多了。
- Stream&Optional不支持此功能,因为它们是基于拉取的。
CompletableFuture不需要解决它,因为它产生0或1结果。
Observable(RxJava 1),Flowable,Flux - 解决它。 常见的策略是:
Buffering(缓冲) - 缓冲所有onNext值,直到下游消耗它。
Drop Recent(删除最近) - 如果下游无法跟上,则删除最近的onNext值。
Use Latest(使用最新) - 仅提供最新的onNext值,如果下游无法跟上,则覆盖任何先前的值。
None(无) - onNext事件在没有任何缓冲或丢弃的情况下写入。
Exception(异常) - 如果下游无法跟上,则发出异常信号。
Observable(RxJava 2) - 没有解决它。 RxJava 1的许多用户使用Observable来处理无法合理地反压或不使用策略来解决它的事件,这会导致意外的异常。 因此,RxJava 2在背压(Flowable)和非Backpressable(Observable)类之间创建了清晰的分离。
Operator Fusion(操作融合)
我们的想法是修改各个生命周期点的阶段链,以消除由库体系结构创建的开销。 所有这些优化都是在内部完成的,因此对于最终用户来说一切都是透明的。
只有RxJava 2和Reactor支持它,但不同。 通常,有两种类型的优化:
宏观融合 - 用一个运算符替换2个以上的运算符。
- 微融合 - 以输出队列结束的运算符和以前队列开始的运算符可以共享相同的队列实例。 例如,不是调用request(1)然后处理onNext():
subscriber can poll for value from parent observable:
Conclusion
创建Stream,CompletableFuture和Optional以解决特定问题。 他们非常善于解决这些问题。 如果它们满足您的需求,您就可以开始了。
然而,不同的问题具有不同的复杂性,并且其中一些需要新技术。 RxJava和Reactor是通用工具,它将帮助您以声明方式解决您的问题,而不是使用非旨在解决此类问题的工具来创建“黑客”。