RxJava 執行緒模型分析

Tony沈哲發表於2017-08-28

RxJava的被觀察者在使用操作符時可以利用執行緒排程器--Scheduler來切換執行緒,例如

        Observable.just("aaa","bbb")
                .observeOn(Schedulers.newThread())
                .map(new Function<String, String>() {
                    @Override
                    public String apply(@NonNull String s) throws Exception {

                        return s.toUpperCase();
                    }
                })
                .subscribeOn(Schedulers.single())
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(@NonNull String s) throws Exception {

                        System.out.println(s);
                    }
                });複製程式碼

被觀察者(Observable、Flowable...)發射資料流之後,其操作符可以在不同的執行緒中加工資料流,最後被觀察者在前臺執行緒中接受並響應資料。

下圖不同的箭頭顏色表示不同的執行緒。

schedulers.png
schedulers.png

一. 執行緒排程器

Schedulers 是一個靜態工廠類,通過分析Schedulers的原始碼可以看到它有多種不同型別的Scheduler。下面是Schedulers的各個工廠方法。

computation()用於CPU密集型的計算任務,但並不適合於IO操作。

    @NonNull
    public static Scheduler computation() {
        return RxJavaPlugins.onComputationScheduler(COMPUTATION);
    }複製程式碼

io()用於IO密集型任務,支援非同步阻塞IO操作,這個排程器的執行緒池會根據需要增長。對於普通的計算任務,請使用Schedulers.computation()。

    @NonNull
    public static Scheduler io() {
        return RxJavaPlugins.onIoScheduler(IO);
    }複製程式碼

trampoline()在RxJava2中跟RxJava1的作用是不同的。在RxJava2中表示立即執行,如果當前執行緒有任務在執行,則會將其暫停,等插入進來的新任務執行完之後,再將原先未完成的任務接著執行。在RxJava1中表示在當前執行緒中等待其他任務完成之後,再執行新的任務。

    @NonNull
    public static Scheduler trampoline() {
        return TRAMPOLINE;
    }複製程式碼

newThread()為每個任務建立一個新執行緒。

    @NonNull
    public static Scheduler newThread() {
        return RxJavaPlugins.onNewThreadScheduler(NEW_THREAD);
    }複製程式碼

single()擁有一個執行緒單例,所有的任務都在這一個執行緒中執行,當此執行緒中有任務執行時,它的任務們將會按照先進先出的順序依次執行。

    @NonNull
    public static Scheduler single() {
        return RxJavaPlugins.onSingleScheduler(SINGLE);
    }複製程式碼

除此之外,還支援自定義的Executor來作為排程器。

    @NonNull
    public static Scheduler from(@NonNull Executor executor) {
        return new ExecutorScheduler(executor);
    }複製程式碼

RxJava 執行緒模型.png
RxJava 執行緒模型.png

Scheduler是RxJava的執行緒任務排程器,Worker是執行緒任務的具體執行者。從Scheduler原始碼可以看到,Scheduler在scheduleDirect()、schedulePeriodicallyDirect()方法中建立了Worker,然後會分別呼叫worker的schedule()、schedulePeriodically()來執行任務。

    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;
    }

    public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initialDelay, long period, @NonNull TimeUnit unit) {
        final Worker w = createWorker();

        final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);

        PeriodicDirectTask periodicTask = new PeriodicDirectTask(decoratedRun, w);

        Disposable d = w.schedulePeriodically(periodicTask, initialDelay, period, unit);
        if (d == EmptyDisposable.INSTANCE) {
            return d;
        }

        return periodicTask;
    }複製程式碼

Worker也是一個抽象類,從上圖可以看到每一種Scheduler會對應一種具體的Worker。

    public abstract static class Worker implements Disposable {

        public Disposable schedule(@NonNull Runnable run) {
            return schedule(run, 0L, TimeUnit.NANOSECONDS);
        }

        public abstract Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit);

        public Disposable schedulePeriodically(@NonNull Runnable run, final long initialDelay, final long period, @NonNull final TimeUnit unit) {
            final SequentialDisposable first = new SequentialDisposable();

            final SequentialDisposable sd = new SequentialDisposable(first);

            final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);

            final long periodInNanoseconds = unit.toNanos(period);
            final long firstNowNanoseconds = now(TimeUnit.NANOSECONDS);
            final long firstStartInNanoseconds = firstNowNanoseconds + unit.toNanos(initialDelay);

            Disposable d = schedule(new PeriodicTask(firstStartInNanoseconds, decoratedRun, firstNowNanoseconds, sd,
                    periodInNanoseconds), initialDelay, unit);

            if (d == EmptyDisposable.INSTANCE) {
                return d;
            }
            first.replace(d);

            return sd;
        }

        public long now(@NonNull TimeUnit unit) {
            return unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        ...

    }複製程式碼

1.1 SingleScheduler

SingleScheduler是RxJava2新增的Scheduler。SingleScheduler中有一個屬性叫executor,它是使用AtomicReference包裝的ScheduledExecutorService。

final AtomicReference<ScheduledExecutorService> executor = new AtomicReference<ScheduledExecutorService>();複製程式碼

在SingleScheduler建構函式中,executor會呼叫lazySet()。

    public SingleScheduler(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
        executor.lazySet(createExecutor(threadFactory));
    }複製程式碼

它的createExecutor()用於建立工作執行緒,可以看到通過SchedulerPoolFactory來建立ScheduledExecutorService。

    static ScheduledExecutorService createExecutor(ThreadFactory threadFactory) {
        return SchedulerPoolFactory.create(threadFactory);
    }複製程式碼

在SchedulerPoolFactory類的create(ThreadFactory factory) 中,使用newScheduledThreadPool執行緒池定義定時器,最大允許執行緒數為1。

    public static ScheduledExecutorService create(ThreadFactory factory) {
        final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, factory);
        if (exec instanceof ScheduledThreadPoolExecutor) {
            ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) exec;
            POOLS.put(e, exec);
        }
        return exec;
    }複製程式碼

在SingleScheduler中每次使用ScheduledExecutorService,其實是使用executor.get()。所以說,single擁有一個執行緒單例。

SingleScheduler會建立一個ScheduledWorker,ScheduledWorker使用jdk的ScheduledExecutorService作為executor。

下面是ScheduledWorker的schedule()方法。使用ScheduledExecutorService的submit()或schedule()來執行runnable。

        @NonNull
        @Override
        public Disposable schedule(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
            if (disposed) {
                return EmptyDisposable.INSTANCE;
            }

            Runnable decoratedRun = RxJavaPlugins.onSchedule(run);

            ScheduledRunnable sr = new ScheduledRunnable(decoratedRun, tasks);
            tasks.add(sr);

            try {
                Future<?> f;
                if (delay <= 0L) {
                    f = executor.submit((Callable<Object>)sr);
                } else {
                    f = executor.schedule((Callable<Object>)sr, delay, unit);
                }

                sr.setFuture(f);
            } catch (RejectedExecutionException ex) {
                dispose();
                RxJavaPlugins.onError(ex);
                return EmptyDisposable.INSTANCE;
            }

            return sr;
        }複製程式碼

1.2 ComputationScheduler

ComputationScheduler使用FixedSchedulerPool作為執行緒池,並且FixedSchedulerPool被AtomicReference包裝了一下。

從ComputationScheduler的原始碼中可以看出,MAX_THREADS是CPU的數目。FixedSchedulerPool可以理解為擁有固定數量的執行緒池,數量為MAX_THREADS。

static { 
     MAX_THREADS = cap(Runtime.getRuntime().availableProcessors(), Integer.getInteger(KEY_MAX_THREADS, 0));
     ......
}

static int cap(int cpuCount, int paramThreads) {
     return paramThreads <= 0 || paramThreads > cpuCount ? cpuCount : paramThreads;
}複製程式碼

ComputationScheduler會建立一個EventLoopWorker。

    @NonNull
    @Override
    public Worker createWorker() {
        return new EventLoopWorker(pool.get().getEventLoop());
    }複製程式碼

其中,getEventLoop()是FixedSchedulerPool中的方法,返回了FixedSchedulerPool中的一個PoolWorker。

        public PoolWorker getEventLoop() {
            int c = cores;
            if (c == 0) {
                return SHUTDOWN_WORKER;
            }
            // simple round robin, improvements to come
            return eventLoops[(int)(n++ % c)];
        }複製程式碼

PoolWorker繼承自NewThreadWorker,它也是執行緒數為1的ScheduledExecutorService。

1.3 IoScheduler

IoScheduler使用CachedWorkerPool作為執行緒池,並且CachedWorkerPool也是被AtomicReference包裝了一下。

CachedWorkerPool是基於RxThreadFactory這個ThreadFactory來建立的。

static {
        ......
        WORKER_THREAD_FACTORY = new RxThreadFactory(WORKER_THREAD_NAME_PREFIX, priority);
        ......
        NONE = new CachedWorkerPool(0, null, WORKER_THREAD_FACTORY);
       ......
}複製程式碼

在RxThreadFactory中,由 prefix 和 incrementAndGet() 來建立新執行緒的名稱。

    @Override
    public Thread newThread(Runnable r) {
        StringBuilder nameBuilder = new StringBuilder(prefix).append('-').append(incrementAndGet());

        String name = nameBuilder.toString();
        Thread t = nonBlocking ? new RxCustomThread(r, name) : new Thread(r, name);
        t.setPriority(priority);
        t.setDaemon(true);
        return t;
    }複製程式碼

IoScheduler建立的執行緒數是不固定的,可以通過IoScheduler 的 size() 來獲得當前的執行緒數。而ComputationScheduler的執行緒數一般情況等於CPU的數目。

    public int size() {
        return pool.get().allWorkers.size();
    }複製程式碼

特別需要的是 ComputationScheduler 和 IoScheduler 都是依賴執行緒池來維護執行緒的,區別就是 IoScheduler 執行緒池中的個數是無限的,由 prefix 和 incrementAndGet() 產生的遞增值來決定執行緒的名字;而 ComputationScheduler 中則是一個固定執行緒數量的執行緒池,資料為CPU的數目,並且不要把 I/O 操作放在 computation() 中,否則 I/O 操作的等待時間會浪費 CPU。

同樣,IoScheduler也會建立EventLoopWorker。

    @NonNull
    @Override
    public Worker createWorker() {
        return new EventLoopWorker(pool.get());
    }複製程式碼

但是這個EventLoopWorker是IoScheduler的內部類,跟ComputationScheduler建立的EventLoopWorker是不一樣的,只是二者的名稱相同罷了。

1.4 NewThreadScheduler

NewThreadScheduler會建立NewThreadWorker。我們看到NewThreadWorker的建構函式也是使用SchedulerPoolFactory。

    public NewThreadWorker(ThreadFactory threadFactory) {
        executor = SchedulerPoolFactory.create(threadFactory);
    }複製程式碼

跟SingleScheduler不同的是,SingleScheduler的executor是使用AtomicReference包裝的ScheduledExecutorService。每次使用時,會呼叫executor.get()。

然而,NewThreadScheduler每次都會建立一個新的執行緒。

1.5 TrampolineScheduler

TrampolineScheduler會建立TrampolineWorker,在TrampolineWorker內部維護著一個PriorityBlockingQueue。任務進入該佇列之前,會先用TimedRunnable封裝一下。

    static final class TimedRunnable implements Comparable<TimedRunnable> {
        final Runnable run;
        final long execTime;
        final int count; // In case if time between enqueueing took less than 1ms

        volatile boolean disposed;

        TimedRunnable(Runnable run, Long execTime, int count) {
            this.run = run;
            this.execTime = execTime;
            this.count = count;
        }

        @Override
        public int compareTo(TimedRunnable that) {
            int result = ObjectHelper.compare(execTime, that.execTime);
            if (result == 0) {
                return ObjectHelper.compare(count, that.count);
            }
            return result;
        }
    }複製程式碼

我們可以看到TimedRunnable實現了Comparable介面,會比較任務的execTime和count。

任務在進入queue之前,count每次都會+1。

final TimedRunnable timedRunnable = new TimedRunnable(action, execTime, counter.incrementAndGet());
queue.add(timedRunnable);複製程式碼

所以,使用TrampolineScheduler時,每次新的任務都會優先執行。

二. 執行緒排程

在預設情況下不做任何執行緒處理,Observable和Observer是處於同一執行緒中的。如果想要切換執行緒的話,可以使用subscribeOn()和observeOn()。

2.1 執行緒排程subscribeOn

subscribeOn通過接收一個Scheduler引數,來指定對資料的處理執行在特定的執行緒排程器Scheduler上。

若多次執行subscribeOn,則只有一次起作用。

點選subscribeOn()的原始碼可以看到,每次呼叫subscribeOn()都會建立一個ObservableSubscribeOn物件。

    public final Observable<T> subscribeOn(Scheduler scheduler) {
        ObjectHelper.requireNonNull(scheduler, "scheduler is null");
        return RxJavaPlugins.onAssembly(new ObservableSubscribeOn<T>(this, scheduler));
    }複製程式碼

ObservableSubscribeOn真正發生訂閱的方法是subscribeActual(Observer<? super T> observer)。

    @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)));
    }複製程式碼

其中,SubscribeOnObserver是下游的Observer通過裝飾器模式生成的。它實現了Observer、Disposable介面。

接下來,在上游的執行緒中執行下游Observer的onSubscribe(Disposable disposabel)方法。

s.onSubscribe(parent);複製程式碼

然後,將子執行緒的操作加入Disposable管理中,加入Disposable後可以方便上下游的統一管理。

parent.setDisposable(scheduler.scheduleDirect(new SubscribeTask(parent)));複製程式碼

在這裡,已經呼叫對應scheduler的scheduleDirect()方法。scheduleDirect() 傳入的是一個Runnable,也就是下面的SubscribeTask。

    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會執行run()對上游的Observable進行訂閱。

此時,已經在對應的Scheduler執行緒中執行了。

source.subscribe(parent);複製程式碼

在RxJava的鏈式操作中,資料的處理是自下而上,這點跟資料發射正好相反。如果多次呼叫subscribeOn,最上面的執行緒切換最晚執行,所以變成了只有第一次切換執行緒才有效。

2.2 執行緒排程observeOn

observeOn同樣接收一個Scheduler引數,用來指定下游操作執行在特定的執行緒排程器Scheduler上。

若多次執行observeOn,則每次均起作用,執行緒會一直切換。

點選observeOn()的原始碼可以看到,每次呼叫observeOn()都會建立一個ObservableObserveOn物件。

    public final Observable<T> observeOn(Scheduler scheduler) {
        return observeOn(scheduler, false, bufferSize());
    }

    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));
    }複製程式碼

ObservableObserveOn真正發生訂閱的方法是subscribeActual(Observer<? super T> observer)。

    @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));
        }
    }複製程式碼

如果scheduler是TrampolineScheduler,上游事件和下游事件會立即產生訂閱。

如果不是的話,scheduler會建立自己的Worker,然後上游事件和下游事件產生訂閱,生成一個ObserveOnObserver物件包裝了下游真正的Observer。

ObserveOnObserver是ObservableObserveOn的內部類,實現了Observer、Runnable介面。跟SubscribeOnObserver不同的是,SubscribeOnObserver實現了Observer、Disposable介面。

在ObserveOnObserver的onNext()中,schedule()執行了具體排程的方法。

        @Override
        public void onNext(T t) {
            if (done) {
                return;
            }

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

        void schedule() {
            if (getAndIncrement() == 0) {
                worker.schedule(this);
            }
        }複製程式碼

其中,worker是當前scheduler建立的Worker,this指的是當前的ObserveOnObserver物件,this實現了Runnable介面。

然後,我們看看Runnable介面的實現方法run(),這個方法是在worker對應的執行緒裡執行的。drainNormal()會取出 ObserveOnObserver 的 queue 裡的資料進行傳送。

        @Override
        public void run() {
            if (outputFused) {
                drainFused();
            } else {
                drainNormal();
            }
        }複製程式碼

下游多次呼叫observeOn()的話,執行緒會一直切換。每一次切換執行緒,都會把對應的Observer物件的各個方法的處理執行在指定的執行緒中。

三. 示例

舉一個多次呼叫subscribeOn、observeOn的例子。

        Observable.just("HELLO WORLD")
                .subscribeOn(Schedulers.single())
                .map(new Function<String, String>() {
                    @Override
                    public String apply(@NonNull String s) throws Exception {

                        s = s.toLowerCase();
                        L.i("map1",s);
                        return s;
                    }
                })
                .observeOn(Schedulers.io())
                .map(new Function<String, String>() {

                    @Override
                    public String apply(String s) throws Exception {

                        s = s+" tony.";
                        L.i("map2",s);
                        return s;
                    }
                })
                .subscribeOn(Schedulers.computation())
                .map(new Function<String, String>() {

                    @Override
                    public String apply(String s) throws Exception {

                        s = s+"it is a test.";
                        L.i("map3",s);
                        return s;
                    }
                })
                .observeOn(Schedulers.newThread())
                .subscribe(new Consumer<String>() {
                    @Override
                    public void accept(@NonNull String s) throws Exception {

                        L.i("subscribe",s);
                        System.out.println(s);
                    }
                });複製程式碼

執行結果.png
執行結果.png

四. 總結

瞭解RxJava的執行緒模型、執行緒排程器、執行緒排程是非常有意義的。能夠幫助我們更合理地使用RxJava。另外,RxJava的執行緒切換結合鏈式呼叫非常方便,比起Java使用執行緒操作實在是簡單太多了。

相關文章