如果调用链中包含多个subscribeOn和observeOn,会是什么情况?

这实际上是一个至关重要的问题,因为在任何情况下,我们都应该弄清楚我们写的每一行代码到底是运行在哪个线程上。这个问题绝对不能含糊。

假设有下面这段伪代码:

[代码]java代码:

01
02
03
04
05
06
07
08
09
10
11
12
13
Observable.create(...)
    .lift1(...)
    .subscribeOn(scheduler1)
    .lift2(...)
    .observeOn(scheduler2)
    .lift3(...)
    .subscribeOn(scheduler3)
    .lift4(...)
    .observeOn(scheduler4)
    .doOnSubscribe(...)
    .subscribeOn(scheduler5)
    .observeOn(scheduler6)
    .subscribe(...);

其中,lift1, lift2, lift3, lift4指的是基于lift实现的变换操作,比如filter, map, reduce等。

那么在这段代码中:

  • lift1, lift2, lift3, lift4指定的代码分别在哪个线程执行?
  • doOnSubscribe指定的代码在哪个线程执行?
  • 产生事件的代码(create指定的代码)在哪个线程执行?
  • 消费事件的代码(subscribe指定的代码)在哪个线程执行?

相信很多同学会觉得这段代码多少有些令人晕眩,在实际中是不太可能出现的。确实,实际中的代码大都没有这么复杂,但弄清它有助于我们理解整个RxJava的实现流程。

上面这幅图表达了一个典型的RxJava调用链中控制流的传递过程。它可以分成两个阶段:

  1. 驱动阶段。整个异步事件流的触发由subscribe开始。它发起了一个反向驱动过程(从下游到上游),跨过每一个中间的Observable和OnSubscribe,到达第一个Observable(产生事件的源头)。对应图中的(1)和(2)。这个阶段一般就是从下游到上游调用一次就结束了。
  2. 事件发射阶段。第一个Observable开始产生事件,然后事件流就开始正向传递,经过每一个中间的Observable,最终到达Subscriber(事件的消费者)。对应图中的(3)。与前一阶段不同,事件从上游往下游传递,不是一次就完了,而是多个事件组成的事件流。

我们分析一下这整个流程,其中有几点需要特别说明一下(注:这里的分析过程涉及RxJava的一些实现细节,如不关心细节可以跳过这一段,直接看后面的结论):

  • 图中的(1)对应的是调用前一级Observable的OnSubscribe.call,是个无返回值的方法,因此可以切换线程,从而变为异步的。所以用虚线表示。
  • 图中的(2)对应的是lift操作指定的Operator.call,是个有返回值的方法(输入一个Subscriber,返回一个新的Subscriber)。因此,它只能同步调用,不能切换线程。所以用实线表示。
  • 图中的(3)对应的是调用后一级Observable对应的Subscriber(onNext, onCompleted, onError),也都是无返回值的方法,因此可以切换线程,从而变为异步的。所以也用虚线表示。
  • observeOn是基于lift实现的,且切换线程的动作发生在Subscriber(onNext, onCompleted, onError),因此它影响(3)流程上在它下游的所有lift变换。
  • subscribeOn不是基于lift实现的,它直接在调用前一级Observable的OnSubscribe时切换线程。因此,它影响(1)流程上在它上游的所有OnSubscribe调用,直到产生事件的源头;然后,(3)流程上的所有lift操作也会在新切换到的线程上,直到碰到一个observeOn操作。
  • doOnSubscribe稍微特殊一点。它虽然是基于lift实现的,但它所指定的代码发生在Operator.call中,不像其它的lift操作,它们指定的代码发生在Subscriber。因此它的执行线程受它下游的subscribeOn的影响。

结合上面的分析,我们沿着前面流程图中箭头所指的方向一路走过去:

  • 首先从调用subscribe方法开始,沿着前面流程图中的(1)->(2)->(1)->(2)…->(1)路径(即驱动阶段),从下游向上游回溯,每经过一个subscribeOn,线程就切换一次;每次切换的线程环境影响这一路径上后面(即上游)的doOnSubscribe指定的代码和产生事件的代码(create指定的代码)。
  • 经过事件的源头(create指定的代码),转而进入事件发射阶段。
  • 然后,再沿着(3)路径(即事件发射阶段),从上游到下游,每经过一个observeOn,线程就切换一次;每次切换的线程环境影响这一路径上后面(即下游)的所有lift操作,直至消费事件的代码(subscribe指定的代码)。

现在,把前面的描述换一种说法,就很容易得到下面的结论了:

  • doOnSubscribe指定的代码和产生事件的代码(create指定的代码),在它们下游最近的一个subscribeOn指定的Scheduler上执行;如果它们下游没有subscribeOn了,那么它们就在调用subscribe方法的那一个线程上执行(注意:是调用subscribe方法的那一个线程,不是subscribe指定的代码执行的那个线程,这是两回事)。
  • 普通的lift操作(比如filter, map, reduce等)和消费事件的代码(subscribe指定的代码),在它们上游最近的一个observeOn指定的Scheduler上执行;如果它们上游没有observeOn了,那么它们就在位于整个调用链最上游的第一个subscribeOn指定的Scheduler上执行;如果没找到subscribeOn调用,那么它们就在调用subscribe方法的那一个线程上执行。

把这些结论应用在本文开始的那段代码上,我们很快能得到:

  • 产生事件的代码(create指定的代码)在scheduler1上执行;
  • lift1和lift2指定的代码在scheduler1上执行;
  • lift3和lift4指定的代码在scheduler2上执行;
  • doOnSubscribe指定的代码在scheduler5上执行;
  • 消费事件的代码(subscribe指定的代码)在scheduler6上执行。
推荐:

RxJava源码初探

一张图解释RxJava中的线程控制的更多相关文章

  1. 一张图解释SQL Server集群、镜像、复制、日志传送

    一张图解释SQL Server集群.镜像.复制.日志传送 本文版权归作者所有,未经作者同意不得转载.

  2. 【转】一张图解析FastAdmin中的表格列表的功能

     一张图解析FastAdmin中的表格列表的功能 功能描述请根据图片上的数字索引查看对应功能说明. 1.时间筛选器如果想在搜索栏使用时间区间进行搜索,则可以在JS中修改修改字段属性,如 {field: ...

  3. 通过三张图了解Redux中的重要概念

    上周利用业余的时间看了看Redux,刚开始有点不适应,一下在有了Action.Reducer.Store和Middleware这么多新的概念. 经过一些了解之后,发现Redux的单向数据里的模式还是比 ...

  4. 一张图解释Hadoop IPC

    基于hadoop2.6.2.... 一张图Server启动,Client访问..... RPC是IPC的一种,IPC还有另外一种LPC,相关请看参考中的3 使用hadoop ipc步骤: 1.定义RP ...

  5. 一张图解释Linux文件系统中硬链接和软链接的区别

    如图所示,硬链接与原始文件共用一个inode,但inode是不跨文件系统的(Ext3.Ext4),每个文件系统都有自己的inode列表.因此,硬链接是没办法跨文件系统的 而软链接不同,软链接相当于重新 ...

  6. 几张图解释明白 Kubernetes Ingress

    来源:K8s技术圈 作者:阳明 Kubernetes Ingress 只是 Kubernetes 中的一个普通资源对象,需要一个对应的 Ingress 控制器来解析 Ingress 的规则,暴露服务到 ...

  7. 一张图解析FastAdmin中的表格列表的功能

    大图: 1.默认生成的CRUD是没有菜单名称和描述显示的,如果需要显示则可以在后台修改,权限管理->菜单规则,给对应菜单的添加上备注信息后即可显示,支持HTML 2.TAB过滤选项卡 在一键生成 ...

  8. linux mysql密码破解一张图解释

  9. 一张图解释---Java多态

    1.向上转型:编译器自动进行,不需要声明 Snowboard s = new Snowboard (); Object o = s; (相当于指向Snowboard的内部Object实例,所有类都继承 ...

随机推荐

  1. $this和self、parent这三个关键词分别代表什么?在哪些场合下使用?

    $this:当前对象 self: 当前类 parent: 当前类的父类 $this在当前类中使用,使用->调用属性和方法. self也在当前类中使用,不过需要使用::调用. 静态属性,不能在类里 ...

  2. kafka-0.9消费者新API

    ## kafka-0.9消费者新API 注:以下仅限kafka版本0.9以上Consumer新版api Consumer自动提交示例: Properties props = new Propertie ...

  3. Tomcat源码浅析

    最近在学习tomcat源码,算是把tomcat的整个流程梳理通了. 从上图来看,tomcat把模块化使用到了极致,配合组件生命周期的管理,让代码看起来结构清晰,而且很容易进行业务扩展. 1.上图的接口 ...

  4. CF 1103B Game with modulo

    题目 $a, x$ 是正整数.显然有 \begin{aligned} x \ge 2x \pmod{a} \implies a \le 2x \end{aligned} 若 $x \le a$ 则 \ ...

  5. [洛谷P3810]【模板】三维偏序(陌上花开)

    题目大意:有$n$个元素,第$i$个元素有三个属性$a_i,b_i,c_i$,设$f(i)=\sum\limits_{i\not = j}[a_j\leqslant a_i,b_j\leqslant ...

  6. 真·APIO2018滚粗记

    有人说只有大佬才会说滚粗啊爆零啊…… 然而我真滚粗真爆零啊…… D1: 听课,然后夏眠. 咦折纸……哦好吧太神仙了. 咦AI……好妙啊好妙啊. 咦二分……哇还有wqs二分这种神奇操作,学学学. (我是 ...

  7. [poj] 1066 Treasure Hunt || 判断直线相交

    原题 在金字塔内有一个宝藏p(x,y),现在要取出这个宝藏. 在金字塔内有许多墙,为了进入宝藏所在的房间必须把墙炸开,但是炸墙只能炸每个房间墙的中点. 求将宝藏运出城堡所需要的最小炸墙数. 判断点和直 ...

  8. 【CZY选讲·Triangle】

    题目描述 长度为的铁丝,你可以将其分成若干段,并把每段都折成一个三角形.你还需要保证三角形的边长都是正整数并且三角形两两相似,问有多少种不同的分法. 数据范围 1≤≤10^6 题解:      ①相 ...

  9. 《R语言实战》读书笔记--第四章 基本数据管理

    本章内容: 操纵日期和缺失值 熟悉数据类型的转换 变量的创建和重编码 数据集的排序,合并与取子集 选入和丢弃变量 多说一句,数据预处理的时间是最长的……确实是这样的,额. 4.1一个示例 4.2创建新 ...

  10. Chromium源码获取与编译

    http://blog.csdn.net/glunoy/article/details/23591047 http://blog.sina.com.cn/s/blog_48f93b530101ergp ...