RxJava的subscribeOn与observeOn源码解析

977 阅读8分钟

前言

上篇文章梳理了整个RxJava/RxSwift的定义、订阅、事件产生-->消费的过程与源码解析,本文主要是想通过前文总结的思路聊聊RxJava的subscribeOn与observeOn的源码实现,如果没看过前文的建议先移步阅读。由于前文的涉及了两个语言的Rx版本,导致篇幅较大,本文先着重总结一下RxJava版本的,后续会出一篇RxSwift版本(ps:本文Rx源码基于RxJava 2.1.16)。

流程

基于前文所解析的Observable.create,我们新增subscribeOn与observeOn两个操作符。随码附赠一个流程图,主要是补充前文解析的代码数据流程加上本次我们聊到的subscribeOn与observeOn。

Observable.create<String> {	// it == CreateEmitter
            Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
            it.onNext("rx")
            it.onComplete()
        }
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe {	// onNext
            Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
            Log.d(TAG, it)
        }

从前文的代码解析可知,RxJava在订阅期间依赖的是抽象方法subscribeActual的实现。事件下发依赖的则是onNext/onError等方法的实现。故下面的文章也会围绕这两方面来总结subscribeOn与observeOn的实现。

subscribeOn

外层调用subscribeOn方法,新建一个ObservableSubscribeOn对象,以自身作为ObservableSubscribeOn的source

// Obserable.java
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> subscribeOn(Scheduler scheduler) {
    ObjectHelper.requireNonNull(scheduler, "scheduler is null");
    return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
}
// ObservableSubscribeOn.java
public final class ObservableSubscribeOn<T> extends AbstractObservableWithUpstream<T, T> {
    final Scheduler scheduler;

    public ObservableSubscribeOn(ObservableSource<T> source, Scheduler scheduler) {
        super(source);
        this.scheduler = scheduler;
    }
    ...

源码:

subscribeActual

在调用subscribeActual时,

  • 创建SubscribeOnObserver对象,用于装饰我们传入的s(上一级的观察者Observer)。
  • 创建一个SubscribeTask对象,将SubscribeOnObserver作为其成员变量,下述代码说明了SubscribeTask是一个Runnable对象
  • 这里有一个利用scheduler设置SubscribeTask对象(Runnable对象)的操作,本文暂不涉及scheduler线程调度器的代码解析,这里可以简单理解为就是调起了线程调度
  • 再来看看SubscribeTask对象(Runnable对象)的run方法,即在线程调度到此Runnable时的逻辑是source.subscribe(parent);source即为ObservableSubscribeOn创建时传入的source,parent即SubscribeOnObserver,也就是订阅在切换线程后继续往上传递了
// ObservableSubscribeOn.java 
@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)));
}
// ObservableSubscribeOn.java 
final class SubscribeTask implements Runnable {
    private final SubscribeOnObserver<T> parent;

    SubscribeTask(SubscribeOnObserver<T> parent) {
        this.parent = parent;
    }

    @Override
    public void run() {
       source.subscribe(parent);
    }
}

源码:

onNext

参照开头的流程图,事件流产生通过onNext往下传递就是通过SubscribeOnObserver的onNext方法。这里只是单纯调用了起成员变量actual(就是在subscribeActual方法中传入的observer,也就是其下一级的观察者)的onNext方法,并没有进行线程处理

static final class SubscribeOnObserver<T> extends AtomicReference<Disposable> implements Observer<T>, Disposable {

   private static final long serialVersionUID = 8094547886072529208L;
   final Observer<? super T> actual;

   final AtomicReference<Disposable> s;

   SubscribeOnObserver(Observer<? super T> actual) {
       this.actual = actual;
       this.s = new AtomicReference<Disposable>();
   }
   ...
   @Override
   public void onNext(T t) {
       actual.onNext(t);
   }
   ...

源码:

observeOn

外层调用observeOn方法,新建一个ObservableObserveOn对象,以自身作为ObservableObserveOn的source

//  Observable.java
@CheckReturnValue
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> observeOn(Scheduler scheduler) {
    return observeOn(scheduler, false, bufferSize());
}

@CheckReturnValue
@SchedulerSupport(SchedulerSupport.CUSTOM)
public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
   ObjectHelper.requireNonNull(scheduler, "scheduler is null");
   ObjectHelper.verifyPositive(bufferSize, "bufferSize");
   return RxJavaPlugins.onAssembly(new ObservableObserveOn<T>(this, scheduler, delayError, bufferSize));
}

源码:

值得一提的是,这里会设置一个默认的bufferSize,该参数是用于定义observeOn内缓存队列大小的,这个后面会讲到。bufferSize()引用的是Flowable.bufferSize()。这里会读取一个系统属性rx2.buffer-size,该值默认情况下默认为128,如果想对此调整的话也可在合适时机修改该值

//  Observable.java
public static int bufferSize() {
    return Flowable.bufferSize();
}

// Flowable.java
/** The default buffer size. */
static final int BUFFER_SIZE;
static {
    BUFFER_SIZE = Math.max(1, Integer.getInteger("rx2.buffer-size", 128));
}

subscribeActual

在调用subscribeActual时,

  • 创建一个Scheduler.Worker对象,这个是封装关于Rx里面线程调度的逻辑。
  • 创建一个包装类ObserveOnObserver对象。
  • 调用source(ObservableObserveOn创建时传入的source)的subscribe以ObserveOnObserver对象为参数,将订阅往上传递
  • 注意:这里没有涉及切换线程的逻辑
//  ObservableObserveOn.java
@Override
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));
    }
}

源码:

onNext

这里先插播一下上文提及的buffersize,其作用是初始化ObserveOnObserver内的队列(SimpleQueue queue)。触发时机是在onSubscribe。该队列是用于后续在onNext的事件触发时用于缓存事件方便线程切换的

//  ObservableObserveOn.java
@Override
public void onSubscribe(Disposable s) {
     if (DisposableHelper.validate(this.s, s)) {
          this.s = s;
          ...
          queue = new SpscLinkedArrayQueue<T>(bufferSize);

          actual.onSubscribe(this);
      }
}

参照开头的流程图,事件流产生通过onNext往下传递经过ObserveOnObserver的onNext方法。onNext中,并没有接着往下调用onNext,而是先将数据缓存到上述提及的队列queue,然后调用schedule()方法

//  ObservableObserveOn.java
@Override
public void onNext(T t) {
    if (done) {
       return;
    }

    if (sourceMode != QueueDisposable.ASYNC) {
        queue.offer(t);
    }
    schedule();
}

schedule()方法中调用worker.schedule,切换线程执行的逻辑。这里传入的this是因为ObserveOnObserver自身也是一个Runnable对象。所以我们可以通过阅读它的run方法得知切换线程后的运行逻辑。

//  ObservableObserveOn.java
static final class ObserveOnObserver<T> extends BasicIntQueueDisposable<T>
    implements Observer<T>, Runnable {
...
void schedule() {
    if (getAndIncrement() == 0) {
       worker.schedule(this);
    }
}

run方法,

  • run方法中标志位outputFused一般为false,这里我们先看drainNormal方法。
  • drainNormal方法中利用死循环将之前缓存到队列(queue)里面的内容逐一调用成员变量actual(就是在subscribeActual方法中传入的observer,也就是其下一级的观察者)的onNext方法。当然,在poll抛出异常时会回调onError处理。注意,这里的逻辑已经在我们最开始设置的scheduler对应的线程运行了。
  • 还有一个比较有意思的线程同步设计,ObserveOnObserver本身也是一个AtomicInteger对象。在schedule()方法中有getAndIncrement(),而在drainNormal第一层循环的最后有addAndGet,这个实际上是AtomicInteger本身的value值+1、-1。以此来保证上述的worker.schedule(this);逻辑在并发情况下只被调用一次,保证线程安全
@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 (;;) {
        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);
        }

        missed = addAndGet(-missed);
        if (missed == 0) {
            break;
        }
    }
}

源码:

总结

至此,subscribeOn与observeOn的源码就解析完了。需要理解的点是:

  • subscribeOn的线程调度发生在subscribeActual,即订阅阶段
  • observeOn的线程调度则发生在onNext/onError等事件加工/消费阶段。 接下来我们用上述提到的源码科学地分析来解决经常在Rx中遇到的线程问题。

常见问题

有时候看到一些教程中提到什么“事件产生的线程取决于subscribeOn”,“事件加工的线程默认与事件消费线程相同,受observeOn影响”等总结。这种常见问题就可以通过结合源码来解析了。

Rx默认会在哪个线程运行?

下列代码可以看到,代码的调用在main线程,最后的输出结果为都在main线程运行。

需要理解的是,Rx并不是默认在main线程,而是默认在当前线程。因为唯有subscribeOn与observeOn两个操作符具有线程调度的能力,准确地说是subscribeOn与observeOn中的scheduler拥有线程调度的能力,两个操作符只是在流程上做出控制而已

所以说,Rx的代码在没有scheduler参与的情况下,没有切换线程,也就在当前线程运行了。

Observable.create<String> {	// it == CreateEmitter
    Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
    it.onNext("I'm ")
    it.onComplete()
}.map {
    Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
    return@map it + "rx"
}.subscribe { // onNext
    Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
    Log.d(TAG, it)
}

输出结果:
事件产生线程:main
事件加工线程:main
事件消费线程:main

判断在哪个线程执行问题

理解了线程切换只在subscribeOn或observeOn有切换线程的操作之后,我们再来看看如何判断每个代码块在什么线程执行的问题。这里还要结合上面总结的:1、subscribeOn的线程调度发生在subscribeActual,即订阅阶段。2、observeOn的线程调度则发生在onNext/onError等事件加工/消费阶段

下面可以看一个稍微复杂一点的例子,我们可以尝试直接通过这个代码判断出所在线程情况:

1、Observable.create传入的事件产生的执行线程

Observable.create的例子中,一般事件产生都是发生在订阅之后,而在subscribeOn的subscribeActual会调用上一级订阅(结合代码,这里source就是ObservableCreate对象),调用其subscribe方法最终会调用ObservableOnSubscribe.subscribe方法,也就是Observable.create传入的这个代码块。经过subscribeOn的subscribeActual后会经历线程切换。故此代码块的调用在Schedulers.io()(RxCachedThreadScheduler-1)的线程。 (ps: 多数情况下,事件产生和订阅不是连续的,所以事件产生也不一定会发生在订阅的线程里。这可能是比较容易混淆的点。

2、第一个map操作符执行线程

触发map操作符的逻辑会在onNext调用之后,而上一级onNext经过observeOn操作符,这里会经历一次线程切换,也就是说此代码块的调用将会在Schedulers.computation()(RxComputationThreadPool-1)线程进行

3、第二个map操作符执行线程

参照2的解析,代码块就会在AndroidSchedulers.mainThread()(main)线程执行。

4、最终的事件消费执行线程

事件消费也是发生在onNext的事件流当中,这里可以寻找其往上最近的一个observeOn定义,因为后续的onNext都没有再经过observeOn操作符了,可以判断线程没有再次切换。故最终的事件消费也是在AndroidSchedulers.mainThread()(main)线程。

Observable.create<String> {	// it == CreateEmitter
            Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
            it.onNext("I'm ")
            it.onComplete()
        }
            .subscribeOn(Schedulers.io())
            .observeOn(Schedulers.computation())
            .map {
                Log.d(TAG, "事件加工线程:${Thread.currentThread().name}")
                return@map it + "rx"
            }
            .observeOn(AndroidSchedulers.mainThread())
            .map {
                Log.d(TAG, "事件加工线程2:${Thread.currentThread().name}")
                return@map "$it!"
            }
            .subscribe {  //  onNext
                Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
                Log.d(TAG, it)
            }

输出结果:
事件产生线程:RxCachedThreadScheduler-1
事件加工线程:RxComputationThreadPool-1
事件加工线程2:main
事件消费线程:main

总结:

通过上述的分析来总结一下:

  • 订阅阶段是一个自下而上的过程,观察阶段(事件加工/事件消费)是一个自上而下的过程。
  • 订阅线程的判断:因为订阅是自下而上的,线程切换的时机也是在subscribeActual方法,所以我们可以找从上往下的第一个subscribeOn操作符来判断线程。
  • 观察线程的判断:自上而下的过程,可以找该代码块往上最近的一次observeOn。这也是很多教程提到的所谓map等操作符、事件消费线程observeOn影响的原因了。

subscribeOn的一次有效性问题

经常还会看到很多人说subscribeOn只有第一次设置的有效。那有效是不是代表着就只会切换这一次线程呢?其实上述的介绍已经有提到了,下面让我们用一个图示说明这个问题吧!(为了方便解析,先将事件加工忽略。)

Observable.create<String> {	// it == CreateEmitter
            Log.d(TAG, "事件产生线程:${Thread.currentThread().name}")
            it.onNext("I'm ")
            it.onComplete()
        }
            .subscribeOn(Schedulers.io())
            .subscribeOn(Schedulers.computation())
            .subscribeOn(AndroidSchedulers.mainThread())
            .subscribe {  //  onNext
                Log.d(TAG, "事件消费线程:${Thread.currentThread().name}")
                Log.d(TAG, it)
            }

流程图画出的是在订阅过程中的数据流,可以看到每次调用subscribeActual都会经历一次线程切换。即“第一次设置的有效”的意思并不是只会有一次线程切换,而是因为最终会切换到第一次设置的线程

最后

本文解析了subscribeOn与observableOn操作符的源码,以及一些常见问题的解析。可以看到subscribeOn与observableOn操作符与线程调度有关,但它们的角色更多的是Rx上面的流程控制。所以我们完全可以将这两个操作符与线程调度scheduler独立开来。后面也会继续写文章总结scheduler的源码解析。

系列文章: