RxJava是对响应式扩展( Reactive Extensions,称之为 ReactiveX )规范的Java 实现,该规范还有其他语言实现:RxJS、Rx.Net、RxScala、RxSwift等等(也即,ReactiveX 定义了规范,其他语言实现规范即可,所以我们这里学习RxJava的架构和设计思维,只需研究ReactiveX 即可)。RxJava是一个通过使用可观察序列来组合异步操作(也即观察者模式,观察序列表示一组观察者,后面会详细介绍),并且基于事件驱动的Java库。它基于观察者模式并扩展了支持数据/事件序列的功能,添加了很多在数据转换时使用的操作符(比如:map、flat等等,像不像Java 8的Stream流式编程)。同时,RxJava 抽象了底层线程模型实现、线程安全操作的实现,让使用方不需要关心底层实现,专注于对业务的处理。
Observable
描述
在ReactiveX 中,我们可以构建一个 Observable 对象,用于表示一个可观察者,该可观察者可以发出一系列的事件,我们可以定义一组观察者,该观察可以监听到可观察者发出的一系列事件,完成自身的逻辑处理。我们来看一张图,该图用于描述可观察者发出的事件以及对事件进行转换处理的结构,描述如下:
- 图中分为上中下三部分
- 图中的上部分的箭头线条表示:时间线,时间从左往右
- 图中的上部分的不同图形表示:由观察者在不同时间点上发出的不同事件对象
- 图中的上部分的最后的截止线表示:可观察者已经完成执行,将不再发出任何事件
- 图中的中间部分表示:事件发出后,对事件执行的转换操作
- 图中的下部分表示:经过转换处理后的事件对象,我们看到有些事件在转换过程中被忽略从而没有输出,而对于X而言,表名在转换时发生了异常
观察者模式
在普通的方法调用中,也即不使用 ReactiveX 的异步编程流程是这样的(同步编程):
- 调用方法
- 获取方法的返回值
- 根据返回值来继续处理
// 方法调用,将其返回值保存到局部变量 returnVal
returnVal = someMethod(itsParameters);
// 用 returnVal 做一些有用的事情
但是,当我们使用 ReactiveX 异步编程模型时 ,流程为:
- 在观察者中定义一个方法,对异步调用的返回值做一些有用的事情
- 将异步调用包装为Observable 可观察者
- 通过订阅(subscribe)将观察者添加到该 Observable 可观察者中,这时触发 Observable 可观察者的异步操作
- 此时由于异步操作由另外的线程执行,同时回调观察者的方法也由别的方法执行,所以当前线程可以继续处理自身的业务,而 Observable 可观察者 的异步线程将在异步操作结束后,回调观察者的对应方法
//(在这个例子中,观察者非常简单,只有一个 onNext 处理程序,表示当可观察者在发出事件后,在该方法中回调处理)
def myOnNext = { it -> it为异步操作返回值,这里可以对其进行业务处理 };
// 定义可观察者 Observable 在其中包装异步操作,itsParameters表示参数
def myObservable = someObservable(itsParameters);
// 将观察者通过订阅的方式放入 Observable,此时将会调用 Observable 的异步操作
myObservable.subscribe(myOnNext);
// 当前线程继续处理业务
观察者可以对 Observable 可观察者 发出的事件进行处理,它们的方法名以onXXX来定义:
- onNext:每当 Observable 发出事件时,Observable 都会调用观察者的该方法,此方法将 Observable 发出的事件对象作为参数传递
- onError: Observable 在处理异步数据时发生了错误,那么将会调用观察者的该方法。此时将不会进一步调用观察者的onNext或onCompleted方法,此方法将 Observable 错误异常对象作为参数传递
- onCompleted:当所有事件全部完成处理且没有发生异常,Observable 即将结束自身处理,那么会在最后一次事件传递后,调用该方法,该方法没有入参
由此可见,我们看到 Observable 可观察者 可能会调用多次观察者对象的 onNext 方法,但是只会调用一次 onError或者onCompleted,取决于是否正常完成。我们来看一个完整的例子:
def myOnNext = { item -> /* item为异步回调结果,使用其完成自身业务处理 */ };
def myError = { throwable -> /* 发生异常时,处理该异常 */ };
def myComplete = { /* 当最后一个事件成功处理时,回调,主要用于清理资源 */ };
def myObservable = someMethod(itsParameters); // 包装异步操作为 Observable 可观察者
myObservable.subscribe(myOnNext, myError, myComplete); // 开始订阅,并触发异步执行
// 当前线程继续处理业务
冷热观察者
冷热观察者用于表示Observable 可观察者什么时候发布事件,也即回调被观察者,这将由Observable 的实现来决定,一个热观察者表示当Observable 可观察者在创建后立即发布事件,不管有没有观察者存在。而冷观察者用于表示有观察者开始订阅后才开始发布事件,这时我们可以保证冷观察者观测到整个事件序列。
小结
我们看到其实所谓的Observable 可观察者和观察者就是一个观察者模式的实现,Observable 可观察者发布事件,观察者观察到事件发生,获取到事件执行事件,只不过对于reactivex而言,这是一个异步过程。读者可以这样理解:多个微服务之间通过MQ来异步通讯,对,就是这样的,只不过这是单机版内存中实现的MQ而已。
Future、Promise、CompletableFuture、Observable
我考虑下在Java中原生实现的Future、Netty中实现的Promise(参考前面介绍的Netty线程模型中Promise的描述)、Java中的异步编程框架CompletableFuture(参考《深入理解java高并发编程》一书对应章节的描述)、reactivex中Observable有何区别:
- Future 用于表示一个异步执行结果,调用方需要调用其get方法来完成结果的获取,并执行相应动作,也即调用方需要参与结果的处理
- Promise 用于表示一个异步执行结果,调用方可以(注意不是必须)调用其get方法来完成结果的获取(继承Java原生的Future),并且可以通过方法addListener方法,向其中增加监听器,当结果完成时,由异步线程来调用监听器完成对结果的处理
- CompletableFuture 用于使用同步方式的写法来处理异步回调,将各个异步回调结果通过thenXX方法形成流式处理,是多线程版本的Stream处理,不关心事件,只将处理结果进行异步回调处理
- Observable 抽象被观察者和观察者,在内存中创建了一个内存MQ,可以支持函数式编程(不关心面向对象,专注于数据流:输入和输出,在Java中通过lambda表达式接受输入产生输出,形成流式编程)和响应式编程(通过响应事件进行编程),注意这两者可以同时具备,也可以独立,比如:我们通过观察者接收到被观察者发送过来的事件,然后对事件进行函数式编程。
- Observable 由于实现了内存MQ,所以自然支持两种模式:推(push)拉(pull)模式:
- push :被观察者在事件发生时,推送给MQ处理(常用模式,因为此时观察者不需要主动询问被观察者事件是否已经发生)
- pull:观察者主动到被观察者拉拉取事件执行,若此时没有产生事件,那么将会阻塞观察者,直到事件发生
在reactivex中,将pull模式,指定为 Iterable,将push模式,指定为 Observable。看如下代码,forEach使用迭代器模式来进行主动拉取数据进行函数式编程(此时如果被观察者中不存在数据(事件),那么将会阻塞),而对于subscribe来说,则使用push模式,当事件没有发生时,将不会阻塞当前线程。
// Iterable pull 模式
getDataFromLocalMemory()
.skip(10) // 函数式编程处理数据
.take(5)
.map({ s -> return s + " transformed" })
.forEach({ println "next => " + it }) // 事件不存在,那么阻塞
// Observable push 模式
getDataFromNetwork()
.skip(10)
.take(5)
.map({ s -> return s + " transformed" })
.subscribe({ println "onNext => " + it }) // 不会阻塞线程
- 最后需要注意的是:reactivex中对普通观察者模式的实现进行了补充:
- 被观察者可以向观察者表明当前没有事件发生,这项功能允许Iterable pull 模式的forEach方法不进行阻塞,直接返回,相当于空调用
- 被观察者可以向观察者表明当前发生了异常,此时在Iterable pull 模式的forEach遍历处理期间处理发生错误时,将会抛出异常,这时将调用观察者的onError方法来处理该异常
所以,通过这两项的补充,ReactiveX Iterable 和 Observable 类型 仅仅只是数据流动的方向不同。所以,对于Iterable pull 模式和Observable push 模式的使用,取决于你怎么用。
Subject
reactivex中的Subject 组件表示一个 bridge component (桥接组件),因为有时候,我们需要一个组件既可以作为观察者观察其他组件的事件,也可以作为被观察者发布事件。这时我们可以使用Subject组件来表示它。因为 Subject 组件订阅了一个 Observable,它将可以接受该 Observable 发布的事件,同时也可以将该事件再发布给自身的观察者对象,如果当前Subject订阅的该 Observable 是冷被观察者(等观察者订阅后再发布事件),那么通过这样的转换,可以使生成的 Subject 从原来冷被观察者 Observable 转变为 热观察者。接下来我们来看看reactivex中定义的基本Subject的四个类型:
AsyncSubject
发出当前AsyncSubject作为观察者定于的被观察者的 Observable(称之为源 Observable) ,在执行完成后发出的最后一个事件(仅是最后一个事件),如果源 Observable 因错误而终止,AsyncSubject 则不会发出任何事件,而只会传递来自源 Observable 的错误,也即调用观察它的观察者的onError方法。如下图所示,当AsyncSubject 观察的Observable 执行完成时,将把最后一个蓝色的事件发布给观察它的观察者。
BehaviorSubject
BehaviorSubject 首先发出源 Observable 最近发出的事件或者设置的默认值事件,然后继续发出源 Observable 稍后发出的任何其他事件,如果源 Observable 因错误而终止,BehaviorSubject则不会向后续观察者发出任何项目,而只会传递来自源 Observable 的异常事件。如下图所示,粉色表示默认值,也即不需要异步计算就存在的值,下面的实现箭头,表示观察BehaviorSubject 的观察者,当订阅时,将首先接收到粉色的默认事件,然后当BehaviorSubject 监听的源 Observable发出其他事件时,将会原样将事件传递给他自己的观察者。最后一个实现箭头表示在后面订阅的观察者,同mq一样,由于前面的事件已经被处理,所以不会接收到粉色的默认事件。
PublishSubject
PublishSubject 仅向自身的观察者发出那些在订阅时间之后由源 Observable(s) 发出的事件。如下图所示。
ReplaySubject
ReplaySubject 向自身的观察者发出源 Observable 发出的所有事件,无论观察者在何时订阅它,都会将曾经接受到的源 Observable 事件发送给它。如下图所示。但是,当Replay的缓冲区中的存储的事件超过其大小后,将会丢弃之前保存的事件。如下图所示。
Scheduler
前面我们介绍Observable 时,知道我们可以在多线程中异步调用观察者和被观察者的方法,这时就需要Scheduler组件来表示底层线程调用器。默认情况下,一个 Observable 将在创建的它并订阅它的线程中通知观察者并回调他们的onXX方法。这时如果我们想要改变回调线程,那么可以使用方法SubscribeOn 通过指定 Scheduler 调度器来改变这种默认行为。 也可以通过方法ObserveOn 指定 Scheduler 调度器,以改变执行异步操作时的线程。如下图所示:
- 图中矩形框中指定的调度器组件将会影响矩形下方的时间线及转换事件的执行操作所在线程
- SubscribeOn(Scheduler ) 方法将指定 Observable 将在哪个线程上执行方法下方的异步操作
- ObserveOn(Scheduler ) 方法将指定 Observable 将在哪个线程上开始回调观察者的onXXX方法
总结
reactivex中使用Observable来表示被观察者,观察者可以实现onXXX方法,并调用subscribe方法来订阅观察者事件
reactivex中使用Subject来表示桥接器,既可以表示被观察者又可以表示观察者
reactivex中使用Scheduler来指定:异步事件的执行线程(SubscribeOn)、观察者回调线程(observeOn)
那么根据reactivex的性质,我们很容易的得出它的使用场景:类似于安卓开发的场景(比如:Swing/JWT图形化开发),在安卓中,由于UI主线程只有一个,它用于渲染UI,不能让他阻塞,这时我们会将IO耗时操作放到异步线程中执行,然后带执行完毕后,回到UI主线程中更新UI,这时就可以使用reactivex的ObserveOn和SubscribeOn来切换线程。看如下代码。
// 使用现成的城市的常量数组,获取每个城市的天气
Observable.from(cities) // 城市常量数组
.mapMany(new Func1<String, Observable<WeatherData>>() {
@Override
public Observable<WeatherData> call(String s) {
return ApiManager.getWeatherData(s);
}
}) // 将其转为Observable观察者
.subscribeOn(Schedulers.threadPoolForIO()) // 定义在IO线程中执行HTTP耗时请求城市天气
.observeOn(AndroidSchedulers.mainThread()) // 请求成功后将回调观察者onXX方法的操作放到UI主线程中执行
.subscribe(new Action1<WeatherData>() {
@Override
public void call(WeatherData weatherData) {
// do your work
}
});
参考书籍
《深入理解Java高并发编程》致力于介绍Java高并发编程方面的知识。由于多线程处理涉及的知识内容十分丰富,因此介绍时必须从Java层面的讲解一直深入到底层的知识讲解。为了帮助读者轻松阅读本书并掌握其中知识,本书做了大量基础知识的铺垫。在第1篇基础知识储备中,主要介绍计算机原理、并发基础、常见语言的线程实现、Java并发入门、JUC之Java线程池、JUC之同步结构、Java NIO详解等内容。在第2篇深入Java并发原理中,详细介绍了JUC包中所有使用的原子类的原理与源码实现;非常关键且容易出错的volatile关键字的原理,从Java、JVM、C、汇编、CPU层面对其进行详细讲解;synchronized在JVM中获取锁和释放锁的流程;JUC包的核心结构——AQS的原理与源码实现,通过逐方法、逐行的解释,帮助读者彻底掌握AQS中提供的获取锁、释放锁、条件变量等操作的实现与原理。最后,详细介绍了JVM中JNI的实现原理,将Java Thread对象中的所有方法在JVM层面的实现流程进行了详细描述,以帮助读者在使用这些方法时,知道底层发生了什么,以及发生异常时如何从容解决问题。