RxJava原始碼解析(二)—執行緒排程器Scheduler

PigCanFly發表於2019-03-04

在RxJava中,有個很重要的概念叫做”執行緒排程器”—Scheduler。它用一種隱式的方法遮蔽掉了我們之前通過回撥方式的執行緒呼叫。我們先看個例子:

Observable<String> ob = Observable.just("str1","str2");
ob.map(new Func1<String, String>() {
@Override
public String call(String t) {
System.out.println("function call " + Thread.currentThread());
return "[" + t + "]";
}})
.observeOn(Schedulers.newThread())
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {}
@Override
public void onError(Throwable e) {}
@Override
public void onNext(String t) {
System.out.println("onNext call " + Thread.currentThread());
System.out.println("onNext "+t);
}
});複製程式碼

程式碼中,我們通過一個字串生成了一個Observable物件,而這個物件我們又通過一個map對映對映成為一個新的Observable物件(這部分的知識請參照第一章RxJava原始碼解析(一)從一個例子開始)。在這之後,我們有通過呼叫observeOn方法設定了一個叫做Schedulers.newThread()的排程器。這個函式的目的是為了告訴你的被觀察者,當你的資料返回的時候需要往哪個執行緒上post你的資料訊息,換句話說,也就是你所定義的Subscriber物件的onCompleted/onError/onNext的執行執行緒。這段程式碼最後輸出:

//output:
function call Thread[main,5,main]//map對映發生在預設執行緒也就是虛擬機器主執行緒中
function call Thread[main,5,main]//map對映發生在預設執行緒也就是虛擬機器主執行緒中
onNext call Thread[RxNewThreadScheduler-1,5,main] // 訊息回撥函式處理在一個新的執行緒中
onNext [str1]
onNext call Thread[RxNewThreadScheduler-1,5,main] // 訊息回撥函式處理在一個新的執行緒中
onNext [str2]複製程式碼

本章,我們將重點關注這個排程器,那麼我們首先要思考的問題是,這個排程器將會提供什麼功能呢?這就要回頭看下我們能用這個排程器幹什麼了?

首先,我們需要排程器去幫助我們生成一個執行緒
其次,當我們以後得到了結果,我們還要需要排程器往排程器執行緒中傳送一個訊息,以便可以執行訂閱者的回撥函式

好的,基於我們上面的需求,我們將看下,在RxJava的排程器實現中,是如何實現我們所需要的功能的。
我們先來看下Observable物件所提供的observeOn函式,這個函式有多個函式過載,最終都會呼叫到三個引數的observeOn方法:

public final Observable<T> observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {
        if (this instanceof ScalarSynchronousObservable) {
            return ((ScalarSynchronousObservable<T>)this).scalarScheduleOn(scheduler);
        }
        return lift(new OperatorObserveOn<T>(scheduler, delayError, bufferSize));
    }複製程式碼

這裡呼叫到了RxJava中一個很重要的操作符號liftlift函式引入了一個叫做Operator的新型別,在上述的例子中這個型別的實現類是一個叫做OperatorObserveOn的策略型別。我們看下這個lift函式定義:

public final <R> Observable<R> lift(final Operator<? extends R, ? super T> operator) {
        return unsafeCreate(new OnSubscribeLift<T, R>(onSubscribe, operator));
    }複製程式碼

我們所傳入的Operator物件最終被包裝成為一個OnSubscribeLift物件,OnSubscribeLift物件是我們非常熟悉的OnSubscribe型別的子類。第一章我們說到OnSubscribe提供一種處理訂閱者註冊訂閱後的策略。按照我們上面的例子,我們呼叫過map函式後呼叫observeOn函式,此時傳入的onSubscribe對應的就是map產生的OnSubscribeMap物件。而引數operator對應observeOn函式中的OperatorObserveOn物件。我們先來看下Operator類的定義:

public interface Operator<R, T> extends Func1<Subscriber<? super R>, Subscriber<? super T>> {
        // cover for generics insanity
    }複製程式碼

Operator也是一種對映關係函式,轉換型別是通過Subscriber<T>->Subscriber<R>。也就是說,Operator是一個直接轉化新的Subscriber的對映函式。這樣就可以在訂閱前攔截訂閱操作。比如:

Observable<String> ob = Observable.just("str1","str2");
        ob.map(new Func1<String, String>() {
            @Override
            public String call(String t) {
                System.out.println("function call " + Thread.currentThread());
                return "[" + t + "]";
            }})
        .lift(new Operator<String,String>(){

            @Override
            public Subscriber<String> call(Subscriber<? super String> st) {
                return new Subscriber<String>() {
                    @Override
                    public void onNext(String t) {
                        long startTime = System.currentTimeMillis();
                        System.out.println("onNext begin");
                        st.onNext(t);//用於監控訂閱者的執行時間
                        System.out.println("onNext execute on next time = "+(System.currentTimeMillis() - startTime)+"ms");
                    }
                };
        }})
        .subscribe(new Subscriber<String>() {
            @Override
            public void onNext(String t) {
                IO.waitTime(5000);
                System.out.println("call onNext "+t);
            }
        });複製程式碼

比如,我們為了監控訂閱者訂閱的時候有多少的時間消耗,我們通過lift函式在我們的訂閱者外包裝了一層Subscriber,這樣我們就可以依賴於包裝的Subscriber物件進行函式監控:

//output:
function call Thread[main,5,main]
onNext begin//開啟監控
call onNext [str1]
onNext execute on next time = 5005ms//監控結束複製程式碼

也就是說,上述的例子中我們的流程圖應該是:

lift後流程圖
lift後流程圖

好的,有了上面的概念,我們可以來看下OperatorObserveOn的程式碼,我們看下它給我們生成了一個什麼樣的訂閱者:

@Override
    public Subscriber<? super T> call(Subscriber<? super T> child) {
     ....
            ObserveOnSubscriber<T> parent = new ObserveOnSubscriber<T>(scheduler, child, delayError, bufferSize);
            parent.init();
            return parent;
  .....
    }複製程式碼

lift函式流程圖
lift函式流程圖

Lift函式執行完後,會將我們所註冊的Subscriber裝飾成為一個ObserveOnSubscriber物件。”lift後流程圖”的紅色框框部分以後註明了這個物件的功能。我們先來看下ObserveOnSubscriber物件的onCompleted/onError三個方法:

 @Override
        public void onCompleted() {
            if (isUnsubscribed() || finished) {
                return;
            }
            finished = true;
            schedule();
        }

        @Override
        public void onError(final Throwable e) {
            if (isUnsubscribed() || finished) {
                RxJavaHooks.onError(e);
                return;
            }
            error = e;
            finished = true;
            schedule();
        }複製程式碼

由於onCompletedonError是互斥的,且只會被呼叫一次,因此會用一個finishedboolean變數來進行攔截,然後呼叫schedule()函式來處理剩下邏輯:

final AtomicLong counter = new AtomicLong();
 protected void schedule() {
            if (counter.getAndIncrement() == 0) {
                recursiveScheduler.schedule(this);
            }
 }複製程式碼

由於counter在呼叫getAndIncrement()後就大於0,因此recursiveScheduler.schedule(this)只會被呼叫一次,recursiveScheduler的定義在ObserveOnSubscriber的構造器中:

 public ObserveOnSubscriber(Scheduler scheduler, Subscriber<? super T> child, boolean delayError, int bufferSize) {
            this.child = child;
            this.recursiveScheduler = scheduler.createWorker();
            ...
        }複製程式碼

scheduler就是我們傳入的Schedulers.newThread()物件,實際上是一個NewThreadScheduler物件:

@Override
    public Worker createWorker() {
        return new NewThreadWorker(threadFactory);//recursiveScheduler的型別是一個NewThreadWorker
    }複製程式碼

可以看出,recursiveScheduler最終會被置為NewThreadWorker型別

public NewThreadWorker(ThreadFactory threadFactory) {
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, threadFactory);
     ....
        executor = exec;
    }複製程式碼

NewThreadWorker構造器中,定義了一個核心執行緒為1ScheduledThreadPool執行緒池。(ScheduledThreadPool是一個很特殊的執行緒池,這個執行緒池的主要是為了支援延遲任務,或者定時任務。)recursiveScheduler.schedule(this)實際上就是呼叫NewThreadWorkerschedule(Action0)方法。

 @Override
    public Subscription schedule(final Action0 action) {
        return schedule(action, 0, null);
    }

    @Override
    public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) {
        if (isUnsubscribed) {
            return Subscriptions.unsubscribed();
        }
        return scheduleActual(action, delayTime, unit);
    }

public ScheduledAction scheduleActual(final Action0 action, long delayTime, TimeUnit unit) {
       ....
        ScheduledAction run = new ScheduledAction(decoratedAction);
        Future<?> f;
        if (delayTime <= 0) {
            f = executor.submit(run);
        } else {
            f = executor.schedule(run, delayTime, unit);
        }
       ...
    }複製程式碼

schedule方法最終會呼叫到scheduleActual方法,action物件會被包裝成為一個ScheduledActionRunable物件提交給執行緒池executor。而執行緒池會呼叫ScheduledActionrun()方法,在run()方法中,又會呼叫Action0call()方法:

@Override
    public void run() {
        try {
            .....
            action.call();
        } catch (OnErrorNotImplementedException e) {
              ....
        }
    }複製程式碼

如果剛才的程式碼已經把你給繞懵了,不要緊,我們再來回顧一下流程:

1. 我們通過lift函式註冊了一個叫做OperatorObserveOnOperator物件

2. lift函式會構造一個叫做OnSubscribeLift的物件用於構造一個Observable物件

3. 當訂閱者Subscriber物件訂閱Observable的時候,根據呼叫鏈,會優先使用OnSubscribeLift物件作為優先處理物件。

4 OnSubscribeLift呼叫call(Subscriber)方法,在該類的call方法中,會通過內部的Operator物件(也就是OperatorObserveOn物件)的Subscriber call(Subscriber)方法,生成一個新的訂閱者ObserveOnSubscriber

5. 新的訂閱者物件ObserveOnSubscriber被OnSubscribeLift物件傳遞給上層的OnSubscribe物件處理,也就是走如RxJava原始碼解析(一)從一個例子開始)中的流程,最後會走到OnSubscribeFromArray物件中,然後遍歷裡面的陣列生產者

6. OnSubscribeFromArray遍歷陣列中的成員,然後呼叫訂閱者的onNextonCompleted。而最終要呼叫到的訂閱者就是ObserveOnSubscriber物件。

7. ObserveOnSubscriber物件的onNext()onCompleted()方法會觸發執行schedule()方法,schedule()方法會呼叫Scheduler.Worker.schedule(Action0)方法,而這個Action0物件就是ObserveOnSubscriber型別

8. 當我們選擇 Schedulers.newThread()排程器的時候,Scheduler.Worker物件實際型別為NewThreadWorker物件,而NewThreadWorker.schedule(Action0)中會將Action0物件包裝成為ScheduledAction物件,ScheduledAction本質是一個Runnable型別,因此它可以被提交到執行緒池中,呼叫ScheduledAction.run()方法,而ScheduledAction.run()方法中,又會呼叫Action0.call()

9. 步驟8中Action0實現的型別為ObserveOnSubscriber型別,此時呼叫ObserveOnSubscriber.call()方法會從queue佇列中讀取onNext引數值並檢測是否已經結束,注意,由於當前函式是由我們排程器生成的Worker物件中的執行緒池呼叫的,因此當前的全部回撥操作都發生在Worker所構建的執行緒中。

#####總結
實際上,我們從上面可以看出,我們通過lift函式所構造出來的ObserveOnSubscriber物件,實際上是生成了一個OnSubscriber的裝飾物件。而這個物件的具體操作,都被封裝到了call()方法中去,換句話說,我們的排程器實際上就是提供一個容器,給我們的call()方法提供上下文。基於我們上述的結論,我們實際上就可以寫出我們自己的排程器:

private static class SchedulerImpl extends Scheduler {
        @Override
        public Worker createWorker() {
            // TODO Auto-generated method stub
            return new WorkerImpl();
        }
    }

private static class WorkerImpl extends Scheduler.Worker {
        @Override
        public void unsubscribe() {}
        @Override
        public boolean isUnsubscribed() {
                        return false;
        }

        @Override
        public Subscription schedule(Action0 action) {
            return schedule(action,0,null);
        }
        @Override
        public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
            Thread thread = new Thread() {
                public void run() {
                    action.call();
                };
            };
            thread.setName("test");
            thread.start();
            return null;
        }
    }複製程式碼

這個排程器的寫法非常的簡單:
1.我們先構建一個Scheduler用於管理我們的Worker
2.observeOn會給我們提供一個ObserveOnSubscriber型別的Action0物件,作為引數呼叫Worker.schedule(Action0 action)方法
3.我們生成了一個獨立的執行緒"test",並線上程中呼叫Action0.call ()方法,這樣就可以將事件傳送到我們所訂閱的真正的Subscriber上了

最後輸出日誌:

output:
call onNext [str1]
call onNext [str2]
call onCompleted Thread[test,5,main]

相關文章