title: RxJava2源码解析(二)
categories:

  • 源码解析
    tags:
  • 源码解析
  • rxJava2

前言

本篇主要解析RxJava的线程切换的原理实现

subscribeOn

首先, 我们先看下subscribeOn()方法, 老样子, 先上Demo

Observable<Integer> observable =
Observable
.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
emitter.onNext(123);
emitter.onComplete();
}
}); observable
.subscribeOn(Schedulers.io())
.subscribe(getObserver());
复制代码

subscribeOn操作符源码里其实是返回了一个ObservableSubscribeOn对象, 而从上篇我们已经知道, 订阅的动作其实在每个ObservablesubscribeActual(observer)中执行, 所以我们直接去看ObservableSubscribeOn中的对应重载方法就行了.

@Override
public void subscribeActual(final Observer<? super T> s) {
final SubscribeOnObserver<T> parent = new SubscribeOnObserver<T>(s); s.onSubscribe(parent); parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));
}
复制代码
final class SubscribeTask implements Runnable {
private final SubscribeOnObserver<T> parent; SubscribeTask(SubscribeOnObserver<T> parent) {
this.parent = parent;
} @Override
public void run() {
source.subscribe(parent);
}
}
复制代码

SubscribeTask是一个Runnable的实现类, 执行内容就是修饰后的Observer订阅上游的动作, 我们先看scheduler.scheduleDirect(runable)方法

@NonNull
public Disposable scheduleDirect(@NonNull Runnable run) {
return scheduleDirect(run, 0L, TimeUnit.NANOSECONDS);
} @NonNull
public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
final Worker w = createWorker(); final Runnable decoratedRun = RxJavaPlugins.onSchedule(run); DisposeTask task = new DisposeTask(decoratedRun, w); w.schedule(task, delay, unit); return task;
}
复制代码

这里createWorker是个抽象方法, 我们需要找到对应的修饰类, 我们返回去看Schedulers.io(), IOIoScheduler的实例, 它的重载方法代码如下

final AtomicReference<CachedWorkerPool> pool;
public Worker createWorker() {
return new EventLoopWorker(pool.get());
}
复制代码

可以看到IO线程实际使用的是一个有线程缓存的线程调度器.它内部通过ScheduledExecutorService实例来尝试重用之前worker开始使用的实例, 由于本篇着重在流程实现原理, 所以略过细节处.
EventLoopWorker中, 我们看下对应的重载方法

public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) {
if (tasks.isDisposed()) {
// don't schedule, we are unsubscribed
return EmptyDisposable.INSTANCE;
} return threadWorker.scheduleActual(action, delayTime, unit, tasks);
}
复制代码

继续往下, 其实这个时候已经是在线程池目标线程执行相关的工作了. 再深入就是线程池的操作了, 所以这里我们不再赘述

public ScheduledRunnable scheduleActual(final Runnable run, long delayTime, @NonNull TimeUnit unit, @Nullable DisposableContainer parent) {
Runnable decoratedRun = RxJavaPlugins.onSchedule(run); ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, parent); if (parent != null) {
if (!parent.add(sr)) {
return sr;
}
} Future<?> f;
try {
if (delayTime <= 0) {
f = executor.submit((Callable<Object>)sr);
} else {
f = executor.schedule((Callable<Object>)sr, delayTime, unit);
}
sr.setFuture(f);
} catch (RejectedExecutionException ex) {
if (parent != null) {
parent.remove(sr);
}
RxJavaPlugins.onError(ex);
} return sr;
}
复制代码

由此我们可以看出来, 每次每个subscribeOn操作符执行的时候, 其实在source.subscribe(parent);订阅动作就做了线程切换, 所以在多次调subscribeOn的时候, 就会一直切换线程, 直到离ObservableSource最近的subscribeOn线程切换生效.

observeOn

废话不说, 我们直接看ObservableObserveOn.subscribeActual(observer)

protected void subscribeActual(Observer<? super T> observer) {
if (scheduler instanceof TrampolineScheduler) {
source.subscribe(observer);
} else {
Scheduler.Worker w = scheduler.createWorker(); source.subscribe(new ObserveOnObserver<T>(observer, w, delayError, bufferSize));
}
}
复制代码

熟悉的配方, 当相同想成的时候, 直接订阅, 而当不同线程的时候, 可以看到我们获取目标切换线程对应的worker实例以及装饰对应的ObserverObserveOnOberver,后面的流程我们心知肚明, 就是Observer层层订阅上去, 然后我们看当碰到最上流的ObservableSource往下执行的时候, 做什么操作.具体我们看ObserveOnOberver代码, 我们这里着重看下onSubscribeonNext方法

@Override
public void onSubscribe(Disposable s) {
if (DisposableHelper.validate(this.s, s)) {
this.s = s;
// 发送的数据是集合队列形式的时候
if (s instanceof QueueDisposable) {
@SuppressWarnings("unchecked")
QueueDisposable<T> qd = (QueueDisposable<T>) s; int m = qd.requestFusion(QueueDisposable.ANY | QueueDisposable.BOUNDARY);
//是同步模式的时候
if (m == QueueDisposable.SYNC) {
sourceMode = m;
queue = qd;
done = true;
actual.onSubscribe(this);
// 线程调度
schedule();
return;
}
// 异步模式
if (m == QueueDisposable.ASYNC) {
sourceMode = m;
queue = qd;
actual.onSubscribe(this);
return;
}
} queue = new SpscLinkedArrayQueue<T>(bufferSize); actual.onSubscribe(this);
}
} @Override
public void onNext(T t) {
// 是否已经调用到onComplete 或者 onError, 如果是, 则不再执行后面的onNext
if (done) {
return;
}
// 如果是非异步操作, 将数据添加到队列中
if (sourceMode != QueueDisposable.ASYNC) {
queue.offer(t);
}
// 线程调度
schedule();
}
复制代码

大概的注释都加在代码上了, 我们再补充看下看onSubscribe方法, 首先判断发送的数据是否属于QueueDisposable, 如果不是, 直接执行下游的onSubscribe,这里我卡了一下, 看不到他的线程切换是在哪里做, 后来往回看, 发现在我们执行ObservableSubscribeOn.subscribeActual(observer)的时候, onSubscribe()方法本身的确不是在切换后的线程内执行的. 但是, 当我们发送的是集合数据, 那么我们需要判断是哪种线程模式进行线程调度.

我们来看具体的schedule()方法代码

void schedule() {
// 判断当前自增值是否为0, 原子性保证worker.schedule(this);不会在调用结束前被重复调用
if (getAndIncrement() == 0) {
worker.schedule(this);
}
}
复制代码

这个时候就是在指定线程内run了, Disposable schedule(@NonNull Runnable run)传入的是个Runnable的实现类, 我们来找重载的run方法

@Override
public void run() {
if (outputFused) {
drainFused();
} else {
drainNormal();
}
} void drainNormal() {
int missed = 1; final SimpleQueue<T> q = queue;
final Observer<? super T> a = actual;
// 无限循环
for (;;) {
// 判断是否被取消, 或者调用onError 或者调用onComplete则退出循环
if (checkTerminated(done, q.isEmpty(), a)) {
return;
}
// 无限循环
for (;;) {
boolean d = done;
T v; try {
// 队列数据分发
v = q.poll();
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
s.dispose();
q.clear();
a.onError(ex);
worker.dispose();
return;
}
boolean empty = v == null;
// 判断是否应该被终止
if (checkTerminated(d, empty, a)) {
return;
} if (empty) {
break;
}
a.onNext(v);
}
// 原子性保证worker.schedule(this)的调用
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
}
} // 判断循环是否终止
boolean checkTerminated(boolean d, boolean empty, Observer<? super T> a) {
// 如果订阅已经被取消, 则清除队列, 终止
if (cancelled) {
queue.clear();
return true;
}
// 如果调用过onError 或者 onComplete
if (d) {
Throwable e = error;
// 默认false
if (delayError) {
// 等到队列为空的时候再调用onError或者onComplete
if (empty) {
if (e != null) {
a.onError(e);
} else {
a.onComplete();
}
worker.dispose();
return true;
}
} else {
// 如果有抛出异常, 走下游的onError
// 线程任务停止
if (e != null) {
queue.clear();
a.onError(e);
worker.dispose();
return true;
}
// 没有, 走下游的onComplete
// 线程任务停止
else if (empty) {
a.onComplete();
worker.dispose();
return true;
}
}
}
// 否则不结束
return false;
}
复制代码

由此我们可以得出结论, observeOn的操作符可以保证我们下流操作线程切换生效

总结

到这里, 我们线程切换的原理大体流程就基本分析完毕了, 可以看出subscribeOn操作符只对上游生效, 而且因为他是在订阅的时候进行线程切换, 而我们每个操作符中间都有订阅动作, 所以越接近我们的ObservableSource的订阅的subscribeOn越是最后生效的. 而observeOn生效在我们的onNext,onComplete, onError方法内, 所以每次的observeOn针对它的下游都可以生效.

RxJava2源码解析(二)的更多相关文章

  1. RxJava2 源码解析(二)

    概述 承接上一篇RxJava2 源码解析(一),本系列我们的目的: 知道源头(Observable)是如何将数据发送出去的.    知道终点(Observer)是如何接收到数据的.    何时将源头和 ...

  2. Android进阶:五、RxJava2源码解析 2

    上一篇文章Android进阶:四.RxJava2 源码解析 1里我们讲到Rxjava2 从创建一个事件到事件被观察的过程原理,这篇文章我们讲Rxjava2中链式调用的原理.本文不讲用法,仍然需要读者熟 ...

  3. Mybatis源码解析(二) —— 加载 Configuration

    Mybatis源码解析(二) -- 加载 Configuration    正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...

  4. Sentinel源码解析二(Slot总览)

    写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...

  5. Android进阶:四、RxJava2 源码解析 1

    本文适合使用过Rxjava2或者了解Rxjava2的基本用法的同学阅读 一.Rxjava是什么 Rxjava在GitHub 主页上的自我介绍是 "a library for composin ...

  6. iOS即时通讯之CocoaAsyncSocket源码解析二

    原文 前言 本文承接上文:iOS即时通讯之CocoaAsyncSocket源码解析一 上文我们提到了GCDAsyncSocket的初始化,以及最终connect之前的准备工作,包括一些错误检查:本机地 ...

  7. jQuery 源码解析二:jQuery.fn.extend=jQuery.extend 方法探究

    终于动笔开始 jQuery 源码解析第二篇,写文章还真是有难度,要把自已懂的表述清楚,要让别人听懂真的不是一见易事. 在 jQuery 源码解析一:jQuery 类库整体架构设计解析 一文,大致描述了 ...

  8. Common.Logging源码解析二

    Common.Logging源码解析一分析了LogManager主入口的整个逻辑,其中第二步生成日志实例工厂类接口分析的很模糊,本随笔将会详细讲解整个日志实例工厂类接口的生成过程! (1).关于如何生 ...

  9. erlang下lists模块sort(排序)方法源码解析(二)

    上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel ...

随机推荐

  1. shell重定向输出的应用

                         shell重定向输出的应用 案例2:重定向输出的应用 2.1问题 本例要求编写一个脚本/root/out.sh,功能特性如下: 执行此脚本显示I love s ...

  2. Linux强大屏幕截图方法,理论能截取任何图形界面,包括登录界面

    众所周知,屏幕截图可以使用“Print Screen”按键,但是,此按键的响应是靠系统的后台服务实现的,Linux在某些场景下,是不响应此按键的. 这里介绍一种更强大的截图方法,它是靠转储X图形环境的 ...

  3. python3(二十) module

    # 在Python中,一个.py文件就称之为一个模块(Module) # 1.最大的好处是大大提高了代码的可维护性. # 2.可以被其他地方引用 # 3.python内置的模块和来自第三方的模块 # ...

  4. 记一次pgsql中查询优化(子查询)

    记一次pgsql的查询优化 前言 这是一个子查询的场景,对于这个查询我们不能避免子查询,下面是我一次具体的优化过程. 优化策略 1.拆分子查询,将需要的数据提前在cte中查询出来 2.连表查询,直接去 ...

  5. pgsql中的lateral使用小结

    pgsql中的lateral 什么是LATERAL 带有LATERAL的SQL的计算步骤 LATERAL在OUTER JOIN中的使用限制(或定义限制) LATERAL的几个简单的例子 总结 举几个我 ...

  6. posix系统线程调度-设置线程优先级

    #include <thread> #include <mutex> #include <iostream> #include <chrono> #in ...

  7. AJ学IOS(02)UI之按钮操作 点击变换 移动 放大缩小 旋转

    不多说,先上图片看效果,AJ分享,必须精品 这个小程序主要实现点击方向键可以让图标上下左右动还有放大缩小以及旋转的功能,点击图片会显示另一张图片. 点击变换 其实用到了按钮的两个状态,再State C ...

  8. Linux环境下django初入

    python -m pip install --upgrade pip 终端中 一. 创建项目: 1.django-admin startproject mysite(第一种比较好) 2.django ...

  9. [算法总结]康托展开Cantor Expansion

    目录 一.关于康托展开 1.什么是康托展开 2.康托展开实现原理 二.具体实施 1.模板 一.关于康托展开 1.什么是康托展开 求出给定一个由1n个整数组成的任意排列在1n的全排列中的位置. 解决这样 ...

  10. 【three.js第二课】页面自适应

    1.在[three.js第一课]的基础上加入以下代码,改变窗口大小时,页面内容会自适应 //加入事件监听器,窗口自适应 window.addEventListener('resize', functi ...