RxJava 2.0 線程切換簡單分析

簡單介紹一下RxJava 2.0的多種流:

Paste_Image.png
1.Completable

特性:這個流沒有數據,只會收到error或者complete
示例:

Completable.complete()
.subscribe(() -> printThread("on complete 1"));

Completable.error(new Callable<Throwable>() {

    @Override
    public Throwable call() throws Exception {
        // TODO Auto-generated method stub
        return new NullPointerException();
    }
})
.subscribe(() -> printThread("on complete 2"),
        (e) -> printThread("on error 2 [" + e.getMessage() + "]"));

輸出:
on complete 1[main]
on error 2 [null][main]
2.Single

特性:這個流只會收到一個數據或者一個error,也就是要不然執行onSuccess要不然就執行onError
示例:

Single.just(1)
    .subscribe(i -> printThread(String.valueOf(i)));

System.out.println("==============");

Single.fromCallable(new Callable<Integer>() {

    @Override
    public Integer call() throws Exception {
        // TODO Auto-generated method stub
        return 1 / 0;
    }
}).subscribe((i, error) -> {
    printThread(String.valueOf(i));
    printThread(String.valueOf(error));
});

輸出:
1[main]
==============
null[main]
java.lang.ArithmeticException: / by zero[main]

可以看到,正常數據下,收到了數據1,出錯的時候,只會收到一個error。
我們看Single的訂閱接口SingleObserver,如下:

public interface SingleObserver<T> {
    void onSubscribe(Disposable d);
    void onSuccess(T value);
    void onError(Throwable e);
}

只會存在2種回調,符合我們的打印輸出。(上面示例代碼僅僅調用的是簡單的單個情況訂閱,查看源碼,最終都封裝成了SingleObserver)

3.Maybe

特性:和Single類似正常流程也是只執行onSuccess,但在出現錯誤的時候,可以選擇是執行onError還是onComplete
示例(正常流程):

Maybe.just(1)
    .subscribe(i -> printThread("success " + i), 
            (e) -> printThread("error " + e), 
            () -> printThread("complete"));

輸出:
success 1[main]

示例(錯誤):

Maybe.fromCallable(() -> {
    return 1 / 0;
}).subscribe(i -> printThread("success " + i), 
            (e) -> printThread("error " + e), 
            () -> printThread("complete"));
輸出:
error java.lang.ArithmeticException: / by zero[main]

我們調用onErrorComplete干預:
Maybe.fromCallable(() -> {
    return 1 / 0;
})
.onErrorComplete()
.subscribe(i -> printThread("success " + i), 
            (e) -> printThread("error " + e), 
            () -> printThread("complete"));
輸出:
complete[main]
4.Flowable

和Observable功能幾乎一模一樣,區別在于:
1.定義的類功能不一樣

Observable 的訂閱者是:

public interface Observer<T> {
    void onSubscribe(Disposable d);
    void onNext(T value);
    void onError(Throwable e);
    void onComplete();
}

public interface Disposable {
    void dispose();
    boolean isDisposed();
}

Flowable的訂閱者是:

public interface Subscriber<T> {
    public void onSubscribe(Subscription s);
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}

public interface Subscription {
    public void request(long n);
    public void cancel();
}

2.Flowable可以通過Subscription對象,調用request(n),響應式拉取數據,來支持背壓特性

示例代碼:

private static int count = 1;

private static boolean isDataEnd() {
    return count > 1000;
}

private static void test() {
    Flowable.create(new FlowableOnSubscribe<Integer>() {

        @Override
        public void subscribe(FlowableEmitter<Integer> e) throws Exception {

            while (!isDataEnd() && !e.isCancelled()) {// 生產數據條件
                while (e.requested() <= 0) {// 如果e.request值是0,說明消費者還沒有消費完畢,我們就休息
                    Thread.sleep(1000);
                }
                printThread(String.format("OUT生產數據[%d]", count));
                e.onNext(count++);
            }
            e.onComplete();
        }
    }, BackpressureStrategy.BUFFER)

            .subscribe(new Subscriber<Integer>() {

                private Subscription mSub;

                @Override
                public void onComplete() {
                    // TODO Auto-generated method stub
                    printThread("消費完畢");
                    unloopMain();
                }

                @Override
                public void onError(Throwable arg0) {
                    // TODO Auto-generated method stub
                    printThread(arg0.getMessage());
                    unloopMain();
                }

                @Override
                public void onNext(Integer value) {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(100);
                        printThread(String.format("IN消費數據[%d]", value));
                        mSub.request(1);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }

                @Override
                public void onSubscribe(Subscription arg0) {
                    // TODO Auto-generated method stub
                    mSub = arg0;
                    mSub.request(1);
                }
            });
    loopMain();
}

private static void loopMain() {
    do {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (needBreak)
            break;
    } while (true);
    System.out.println("END");
}

private static void unloopMain() {
    needBreak = true;
}

輸出如下:
OUT生產數據[1][main]
IN消費數據[1][main]
OUT生產數據[2][main]
IN消費數據[2][main]
OUT生產數據[3][main]
IN消費數據[3][main]
OUT生產數據[4][main]
IN消費數據[4][main]
OUT生產數據[5][main]

流程分析:
1.調用Flowable.create生產我們的數據流,里面有個新的類型,如下:

public interface FlowableEmitter<T> extends Emitter<T> {
    void setDisposable(Disposable s);
    void setCancellable(Cancellable c);
    long requested();
    boolean isCancelled();
    FlowableEmitter<T> serialize();
}

其他方法和Observable發射器的功能類似,我們主要需要requested()方法,獲取當前請求個數,如果為0代表還在消費數據,不需要新的數據,我們就休息。
2.BackpressureStrategy,代表支持背壓的策略,如下:

public enum BackpressureStrategy {
    //結合onBackpressureXXX()才會生效
    MISSING,//在onSubcription時候,s.request(Long.MAX_VALUE);設置最大值。導致一直生產數據。
    //直接拋出異常,如果數據溢出
    ERROR,
    //所有數據會保存到緩存里面
    BUFFER,
    //丟棄最新的
    DROP,
   //保存最新的,覆蓋老的
    LATEST
}

3.背壓策略,一定要在線程變換之前去調用,線程變換后,收到的訂閱者發生了變化,不是同一個。上面代碼,因為是同一個線程,調用request(1),將直接設置上游數據生產者FlowableEmitter的值為1,但如果是切換線程了,將無法直接影響,增加代碼如下:

Flowable.create(...)
.observeOn(Schedulers.computation())//切換線程
.subscribe(...);

打印如下:
...
OUT生產數據[127][requested = 2][main]
OUT生產數據[128][requested = 1][main]
IN消費數據[1][RxComputationThreadPool-1]
IN消費數據[2][RxComputationThreadPool-1]
IN消費數據[3][RxComputationThreadPool-1]
IN消費數據[4][RxComputationThreadPool-1]
...

我們可以看到,下游的request(1)并不會影響上游的值,上游使用了默認值128的緩存大小。先生產了128個數據,再開始消費。兩個疑問解答:
1.128怎么來的,在調用BackpressureStrategy.Buffer時候,生成的FlowableEmitter實際類型是BufferAsyncEmitter,它默認值就是128

2.我們怎么去控制這個值呢,既然下游影響不到這個大小,可以通過如下代碼:

...
.observeOn(Schedulers.computation(), false, 4)
...

打印輸出,如下:
...
OUT生產數據[1][requested = 4][main]
OUT生產數據[2][requested = 3][main]
OUT生產數據[3][requested = 2][main]
OUT生產數據[4][requested = 1][main]
IN消費數據[1][RxComputationThreadPool-1]
IN消費數據[2][RxComputationThreadPool-1]
IN消費數據[3][RxComputationThreadPool-1]
IN消費數據[4][RxComputationThreadPool-1]
OUT生產數據[5][requested = 3][main]
OUT生產數據[6][requested = 2][main]
OUT生產數據[7][requested = 1][main]
IN消費數據[5][RxComputationThreadPool-1]
IN消費數據[6][RxComputationThreadPool-1]
IN消費數據[7][RxComputationThreadPool-1]
...

這里看到,我們只生產了4個數據,就消費了,以后就是生產3個,這是因為在我們調用observeOn()生成的內部對象FlowableObserveOn里面有個limit = prefetch - (prefetch >> 2); prefetch實際就是傳入的buffersize。如下:

void runAsync(){
  ...
  e++;
  if (e == limit) {
    if (r != Long.MAX_VALUE) {
        r = requested.addAndGet(-e);
    }
    s.request(e);
    e = 0L;
  }
}

這個方法在內部類 FlowableObserveOn$ObserveOnSubscriber,我們異步調用sub.request(n)將最終觸發到runAsync(),它會去設置requested也就是上游的數據,所以這個n將無法直接反應到上游,而同步的n是直接設置給上游了

分析線程變換:

使用最簡單的Single來進行探究,代碼如下:

private static void syncRx(){
    Single.fromCallable(() -> {
        printThread("生產數據");
        return "s";
    })
    .subscribe((s) -> {
        printThread("消費數據");
    }); 
}

private static void printThread(String msg){
    System.out.println(String.format("[%s][%s]", msg, Thread.currentThread().getName()));
}

打印輸出:
[生產數據][main]
[消費數據][main]

查看源碼,Single.fromCallable 生成的就是SingleFromCallable對象,訂閱表達式生成的是ConsumerSingleObserver對象,代碼變換如下:

private static void syncChangeRx() {
    new SingleFromCallable<String>(() -> {
        printThread("生產數據");
        return "s";
    }).subscribe(new ConsumerSingleObserver<>((s) -> {
        printThread("消費數據");
    }, Functions.ERROR_CONSUMER));
}

輸出打印和上面一模一樣,所以點開方法subscribe(),如下:

public final void subscribe(SingleObserver<? super T> subscriber) {
    ...
    try {
        subscribeActual(subscriber);
    } catch (NullPointerException ex) {
        throw ex;
    } catch (Throwable ex) {
        ...
    }
}

最終調用的是抽象方法 subscribeActual(),而我們知道我們來源于SingleFromCallable,所以實際實現在SingleFromCallable.subscribeActual()方法里面,如下:

@Override
protected void subscribeActual(SingleObserver<? super T> s) {
    s.onSubscribe(EmptyDisposable.INSTANCE);
    try {
        T v = callable.call();
        if (v != null) {
            s.onSuccess(v);
        } else {
            s.onError(new NullPointerException("The callable returned a null value"));
        }
    } catch (Throwable e) {
        ...
    }
}

里面也很簡單,參數 s,就是我們自己定義的后生產的ConsumerSingleObserver對象,callable就是我們定義的生產對象,所以下游的訂閱動作,如下:

1.訂閱觸發流程
2.進入真實SingleFromCallable.subscribeActual()
3.調用callable.call()生產數據或者異常
4.回調給ConsumerSingleObserver,我們自己的消費者
5.完成結束

以上就是最簡單的單線程調用了,在以上的基礎上,我們增加一個線程切換,如下:

private static void asyncChangeRx() {
    new SingleFromCallable<String>(() -> {
        printThread("生產數據");
        return "s";
    })
    .observeOn(Schedulers.computation())
    .subscribe(new ConsumerSingleObserver<>((s) -> {
        printThread("消費數據");
    }, Functions.ERROR_CONSUMER));
}

打印輸出:
[生產數據][main]
[消費數據][RxComputationThreadPool-1]

查看源碼,我們可以知道,observeOn也生產了一個新的包裝流SingleObserveOn,變換,如下:

private static void asyncChangeRx() {
    SingleSource<String> producer = new SingleFromCallable<>(() -> {
            printThread("生產數據");
            return "s";
        });
    SingleObserver<String> consumer = new ConsumerSingleObserver<>((s) -> {
        printThread("消費數據");
    }, Functions.ERROR_CONSUMER);
    
    new SingleObserveOn<>(producer, Schedulers.computation())
    .subscribe(consumer);
}

這樣也就是具有線程變換功能的SingleObserveOn,包裹起了原始的生產者SingleFromCallable,其他不變,因此我們先認為是單一線程模型可以大概推出:

consumer訂閱  --->
SingleObserveOn.subscribeActual()   --->
SingleFromCallable.subscribeActual()

所以真正線程變換就在SingleObserveOn.subscribeActual()里面,實現如下:

@Override
protected void subscribeActual(final SingleObserver<? super T> s) {
  source.subscribe(new ObserveOnSingleObserver<T>(s, scheduler));
}


這里SingleObserver<? super T> s就是我們自己的 consumer, 而source就是我們的 producer,一定要注意,上面這個訂閱過程會導致上游產生數據,因此將觸發ObserveOnSingleObserver.onSuccess(T t),我們再看具體實現代碼,如下:

static final class ObserveOnSingleObserver<T> extends AtomicReference<Disposable>
implements SingleObserver<T>, Disposable, Runnable {

    ...
    final SingleObserver<? super T> actual;//我們實際的訂閱者
    final Scheduler scheduler;//我們設定的線程執行類

    T value;//保存上游的數據
    Throwable error;//保存上游的錯誤


    @Override
    public void onSuccess(T value) {
        this.value = value;//拿到上游數據
        Disposable d = scheduler.scheduleDirect(this);//要求線程執行自己
        DisposableHelper.replace(this, d);
    }

    @Override
    public void onError(Throwable e) {
        this.error = e;
        Disposable d = scheduler.scheduleDirect(this);
        DisposableHelper.replace(this, d);
    }

    @Override
    public void run() {//這里異步執行原有的調用流程
        Throwable ex = error;
        if (ex != null) {
            actual.onError(ex);
        } else {
            actual.onSuccess(value);
        }
    }
    
    ...
}

生產數據后,ObserveOnSingleObserver本身繼承了Runnable,將同步調用的流程封裝在了run()方法里面,再叫scheduler去執行自己,完成了線程切換。

線程切換最簡單的整個流程,就是以上調用,如果加上生產者也要切換線程,也是一樣的,它有個對象,SingleSubscribeOn來包裝流,包裝過程偽代碼如下:

從調用鏈最后開始,往上包裝:
SingleObserveOn  --包含-->
SingleSubscribeOn --包含-->
producer

訂閱過程,又最后往上 被包裝:
consumer  --被包含-->
ObserveOnSingleObserver--被包含-->  負責消費者切換
SubscribeOnObserver   負責生產者切換

SubscribeOnObserver 里面也很簡單,也是run方法,如下:

@Override
public void run() {
  source.subscribe(this);//訂閱就是生產數據
}

線程變換總結:
1.訂閱將會觸發生產
2.將上游的訂閱過程,封裝到runnable,再交由scheduler去執行
3.將下游的消費過程,封裝到runnable,再交由scheduler去執行

其他思考:
多次線程變換,生產數據會在哪次里面?,而消費過程會在哪次里面?
示例代碼:

public static void main(String[] args) {
    asyncChangeRx2();
    loopMain();
}

    
private static void asyncChangeRx2() {
    Single.fromCallable(() -> {
            printThread("生產數據");
            return "s";
        })
        .subscribeOn(myScheduler("生產包裝線程1"))
        .subscribeOn(myScheduler("生產包裝線程2"))
        .observeOn(myScheduler("消費包裝線程1"))
        .observeOn(myScheduler("消費包裝線程2"))
        .subscribe((s) -> {
            printThread("消費數據");
            unloopMain();
        });
    
}

private static Scheduler myScheduler(String name) {
    return new Scheduler() {

        @Override
        public Worker createWorker() {
            // TODO Auto-generated method stub
            return new NewThreadWorker(new ThreadFactory() {

                @Override
                public Thread newThread(Runnable r) {
                    // TODO Auto-generated method stub

                    return new Thread(new HookRun(r), name);
                }
            });
        }
    };
}

//簡單打印活動線程
private static class HookRun implements Runnable {

    private Runnable mRealRun;

    public HookRun(Runnable r) {
        // TODO Auto-generated constructor stub
        mRealRun = r;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(String.format("執行Run[%s]", Thread.currentThread().getName()));
        mRealRun.run();
    }

}


private static void printThread(String msg) {
    System.out.println(String.format("[%s][%s]", msg, Thread.currentThread().getName()));
}

private static void loopMain() {
    do {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (needBreak)
            break;
    } while (true);
    System.out.println("END");
}

private static void unloopMain() {
    needBreak = true;
}

private static boolean needBreak;
}

輸出:
執行Run[生產包裝線程2]
執行Run[生產包裝線程1]
[生產數據][生產包裝線程1]
執行Run[消費包裝線程1]
執行Run[消費包裝線程2]
[消費數據][消費包裝線程2]
END

說明生產最終在第一次包裝里面,消費在最后一次包裝里面,符合我們剛才分析的包裝過程偽代碼的方向。離活動(生產或者消費)最近的一次線程切換包裝負責執行

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評論 6 545
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,795評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,773評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,106評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,282評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,793評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,507評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,741評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,929評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,482評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,702評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,806評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • ……同桌可以無聊到一張桌子上傳紙條;和同桌共同抄抄過一個人的作業,TA罵你抄德慢;你和同桌的共同財產就是那幾支筆;...
    希雨啾閱讀 401評論 2 2
  • http://www.cocoachina.com/ios/20161116/18099.html
    Code丶Ling閱讀 371評論 0 0
  • 早上,急急忙忙,用現有的材料擺了一下。
    土坑1214閱讀 327評論 2 0