Rxjava2入門教程五:Flowable背壓支持——對Flowable最全面而詳細的講解

如需下載源碼,請訪問
https://github.com/fengchuanfang/Rxjava2Tutorial
文章原創,轉載請注明出處:
Rxjava2入門教程五:Flowable背壓支持——對Flowable最全面而詳細的講解


背壓(backpressure)

當上下游在不同的線程中,通過Observable發射,處理,響應數據流時,如果上游發射數據的速度快于下游接收處理數據的速度,這樣對于那些沒來得及處理的數據就會造成積壓,這些數據既不會丟失,也不會被垃圾回收機制回收,而是存放在一個異步緩存池中,如果緩存池中的數據一直得不到處理,越積越多,最后就會造成內存溢出,這便是響應式編程中的背壓(backpressure)問題。
例如,運行以下代碼:

   public void demo1() {
        Observable
                .create(new ObservableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                        int i = 0;
                        while (true) {
                            i++;
                            e.onNext(i);
                        }
                    }
                })
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(Integer integer) throws Exception {
                        Thread.sleep(5000);
                        System.out.println(integer);
                    }
                });
    }

創建一個可觀察對象Observable在Schedulers.newThread()的線程中不斷發送數據,而觀察者Observer在Schedulers.newThread()的另一個線程中每隔5秒接收打印一條數據。
運行后,查看內存使用如下:

backpressure.gif

由于上游通過Observable發射數據的速度大于下游通過Consumer接收處理數據的速度,而且上下游分別運行在不同的線程中,下游對數據的接收處理不會堵塞上游對數據的發射,造成上游數據積壓,內存不斷增加,最后便會導致內存溢出。

Flowable

既然在函數響應式編程中會產生背壓(backpressure)問題,那么在函數響應式編程中就應該有解決方案。
Rxjava2相對于Rxjava1最大的更新就是把對背壓問題的處理邏輯從Observable中抽取出來產生了新的可觀察對象Flowable。

在Rxjava2中,Flowable可以看做是為了解決背壓問題,在Observable的基礎上優化后的產物,與Observable不處在同一組觀察者模式下,Observable是ObservableSource/Observer這一組觀察者模式中ObservableSource的典型實現,而Flowable是Publisher與Subscriber這一組觀察者模式中Publisher的典型實現。

所以在使用Flowable的時候,可觀察對象不再是Observable,而是Flowable;觀察者不再是Observer,而是Subscriber。Flowable與Subscriber之間依然通過subscribe()進行關聯。

雖然在Rxjava2中,Flowable是在Observable的基礎上優化后的產物,Observable能解決的問題Flowable也都能解決,但是并不代表Flowable可以完全取代Observable,在使用的過程中,并不能拋棄Observable而只用Flowable。

由于基于Flowable發射的數據流,以及對數據加工處理的各操作符都添加了背壓支持,附加了額外的邏輯,其運行效率要比Observable慢得多。

只有在需要處理背壓問題時,才需要使用Flowable。

由于只有在上下游運行在不同的線程中,且上游發射數據的速度大于下游接收處理數據的速度時,才會產生背壓問題;
所以,如果能夠確定:
1、上下游運行在同一個線程中,
2、上下游工作在不同的線程中,但是下游處理數據的速度不慢于上游發射數據的速度,
3、上下游工作在不同的線程中,但是數據流中只有一條數據
則不會產生背壓問題,就沒有必要使用Flowable,以免影響性能。

類似于Observable,在使用Flowable時,也可以通過create操作符創建發射數據流,代碼如下:

public void demo2() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        System.out.println("發射----> 1");
                        e.onNext(1);
                        System.out.println("發射----> 2");
                        e.onNext(2);
                        System.out.println("發射----> 3");
                        e.onNext(3);
                        System.out.println("發射----> 完成");
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER) //create方法中多了一個BackpressureStrategy類型的參數
                .subscribeOn(Schedulers.newThread())//為上下游分別指定各自的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {   //onSubscribe回調的參數不是Disposable而是Subscription
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這么設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收----> " + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

運行結果如下:

System.out: 發射----> 1
System.out: 發射----> 2
System.out: 發射----> 3
System.out: 發射----> 完成
System.out: 接收----> 1
System.out: 接收----> 2
System.out: 接收----> 3
System.out: 接收----> 完成

發射與處理數據流在形式上與Observable大同小異,發射器中均有onNext,onError,onComplete方法,訂閱器中也均有onSubscribe,onNext,onError,onComplete方法。

但是在細節方面還是有三點不同
一、create方法中多了一個BackpressureStrategy類型的參數。
二、訂閱器Subscriber中,方法onSubscribe回調的參數不是Disposable而是Subscription,多了行代碼:

s.request(Long.MAX_VALUE);

三、Flowable發射數據時,使用特有的發射器FlowableEmitter,不同于Observable的ObservableEmitter

正是這三點不同賦予了Flowable不同于Observable的特性。

BackpressureStrategy背壓策略

在通過create操作符創建Flowable時,多了一個BackpressureStrategy類型的參數,BackpressureStrategy是個枚舉,源碼如下:

package io.reactivex;

/**
 * Represents the options for applying backpressure to a source sequence.
 */
public enum BackpressureStrategy {
    /**
     * OnNext events are written without any buffering or dropping.
     * Downstream has to deal with any overflow.
     * <p>Useful when one applies one of the custom-parameter onBackpressureXXX operators.
     */
    MISSING,
    /**
     * Signals a MissingBackpressureException in case the downstream can't keep up.
     */
    ERROR,
    /**
     * Buffers <em>all</em> onNext values until the downstream consumes it.
     */
    BUFFER,
    /**
     * Drops the most recent onNext value if the downstream can't keep up.
     */
    DROP,
    /**
     * Keeps only the latest onNext value, overwriting any previous value if the
     * downstream can't keep up.
     */
    LATEST
}

當上游發送數據的速度快于下游接收數據的速度,且運行在不同的線程中時,Flowable通過自身特有的異步緩存池,來緩存沒來得及處理的數據,緩存池的容量上限為128,在Flowable源碼的開頭即可看到

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

不同于Observable,其異步緩存沒有容量限制,對于沒來得及處理的數據可以一直向里面添加,數據過多就會產生內存溢出(OOM)。

BackpressureStrategy的作用便是用來設置Flowable通過異步緩存池緩存數據的策略。在源碼FlowableCreate類中,可以看到五個泛型分別對應五個java類

MISSING   ----> MissingEmitter
ERROR     ----> ErrorAsyncEmitter
DROP      ----> DropAsyncEmitter
LATEST    ----> LatestAsyncEmitter
BUFFER    ----> BufferAsyncEmitter

通過代理模式對原始的發射器進行了包裝。

@Override
    public void subscribeActual(Subscriber<? super T> t) {
        BaseEmitter<T> emitter;

        switch (backpressure) {
            case MISSING: {
                emitter = new MissingEmitter<T>(t);
                break;
            }
            case ERROR: {
                emitter = new ErrorAsyncEmitter<T>(t);
                break;
            }
            case DROP: {
                emitter = new DropAsyncEmitter<T>(t);
                break;
            }
            case LATEST: {
                emitter = new LatestAsyncEmitter<T>(t);
                break;
            }
            default: {
                emitter = new BufferAsyncEmitter<T>(t, bufferSize());
                break;
            }
        }

        t.onSubscribe(emitter);
        try {
            source.subscribe(emitter);
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            emitter.onError(ex);
        }
    }

ERROR

對應于ErrorAsyncEmitter<T>類,在其源碼

static final class ErrorAsyncEmitter<T> extends NoOverflowBaseAsyncEmitter<T> {
        private static final long serialVersionUID = 338953216916120960L;

        ErrorAsyncEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

        @Override
        void onOverflow() {
            onError(new MissingBackpressureException("create: could not emit value due to lack of requests"));
        }

    }

onOverflow方法中可以看到
在此策略下,如果放入Flowable的異步緩存池中的數據超限了,則會拋出MissingBackpressureException異常。

運行如下代碼:

public void demo3() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        for (int i = 1; i <= 129; i++) {
                            e.onNext(i);
                        }
                        e.onComplete();
                    }
                }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這么設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException ignore) {
                        }
                        System.out.println(integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

創建并通過Flowable發射129條數據,Subscriber的onNext方法睡10秒之后再開始接收,運行后會發現控制臺打印如下異常:

W/System.err: io.reactivex.exceptions.MissingBackpressureException: create: could not emit value due to lack of requests
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableCreate$ErrorAsyncEmitter.onOverflow(FlowableCreate.java:411)
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableCreate$NoOverflowBaseAsyncEmitter.onNext(FlowableCreate.java:377)
W/System.err:     at net.fbi.rxjava2.RxJava2Demo$6.subscribe(RxJava2Demo.java:103)
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableCreate.subscribeActual(FlowableCreate.java:72)
W/System.err:     at io.reactivex.Flowable.subscribe(Flowable.java:12218)
W/System.err:     at io.reactivex.internal.operators.flowable.FlowableSubscribeOn$SubscribeOnSubscriber.run(FlowableSubscribeOn.java:82)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59)
W/System.err:     at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51)
W/System.err:     at java.util.concurrent.FutureTask.run(FutureTask.java:237)
W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:154)
W/System.err:     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:269)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
W/System.err:     at java.lang.Thread.run(Thread.java:818)

如果將Flowable發射數據的條數改為128,則不會出現此異常。

DROP

對應于DropAsyncEmitter<T>類,通過DropAsyncEmitter類和它父類NoOverflowBaseAsyncEmitter的源碼

    static final class DropAsyncEmitter<T> extends NoOverflowBaseAsyncEmitter<T> {
        private static final long serialVersionUID = 8360058422307496563L;

        DropAsyncEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

        @Override
        void onOverflow() {
            // nothing to do
        }
    }
abstract static class NoOverflowBaseAsyncEmitter<T> extends BaseEmitter<T> {

        private static final long serialVersionUID = 4127754106204442833L;

        NoOverflowBaseAsyncEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

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

            if (t == null) {
                onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
                return;
            }

            if (get() != 0) {
                actual.onNext(t);
                BackpressureHelper.produced(this, 1);
            } else {
                onOverflow();
            }
        }

        abstract void onOverflow();
    }

可以看到,DropAsyncEmitter的onOverflow是個空方法,沒有執行任何操作,父類的onNext中,在判斷get() != 0,即緩存池未滿的情況下,才會讓被代理類調用onNext方法。
所以在此策略下,如果Flowable的異步緩存池滿了,會丟掉上游發送的數據。
運行如下代碼:

public void demo4() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        System.out.println(threadName + "開始發射數據" + System.currentTimeMillis());
                        for (int i = 1; i <= 500; i++) {
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                            try {
                                Thread.sleep(100);//每隔100毫秒發射一次數據
                            } catch (Exception ex) {
                                e.onError(ex);
                            }
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.DROP)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這么設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(300);//每隔300毫秒接收一次數據
                        } catch (InterruptedException ignore) {
                        }
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

通過創建Flowable發射500條數據,每隔100毫秒發射一次,并記錄開始發射和結束發射的時間,下游每隔300毫秒接收一次數據,運行后,控制臺打印日志如下:

GIF111.gif

通過日志

1.jpg

我們可以發現Subscriber在接收完第128條數據后,再次接收的時候已經到了288,而這之間的160條數據正是因為緩存池滿了而被丟棄掉了。
那么問題來了,當Flowable在發射第129條數據的時候,Subscriber已經接收了42條數據了,第129條數據為什么沒有放入緩存池中呢?日志如下:


2.jpg

那是因為緩存池中數據的清理,并不是Subscriber接收一條,便清理一條,而是存在一個延遲,等累積一段時間后統一清理一次。也就是Subscriber接收到第96條數據時,緩存池才開始清理數據,之后Flowable發射的數據才得以放入。

3.jpg

查看日志可以發現,Subscriber接收到第96條數據后,Flowable發射第288條數據。而第128到288之間的數據,正好處于緩存池存滿的狀態,而被丟棄,所以Subscriber在接收完第128條數據之后,接收到的是第288條數據,而不是第129條。

LATEST

對應于LatestAsyncEmitter<T>類
與Drop策略一樣,如果緩存池滿了,會丟掉將要放入緩存池中的數據,不同的是,不管緩存池的狀態如何,LATEST都會將最后一條數據強行放入緩存池中,來保證觀察者在接收到完成通知之前,能夠接收到Flowable最新發射的一條數據。
將上述代碼中的DROP策略改為LATEST:

public void demo5() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        System.out.println(threadName + "開始發射數據" + System.currentTimeMillis());
                        for (int i = 1; i <= 500; i++) {
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                            try {
                                Thread.sleep(100);
                            } catch (Exception ignore) {
                            }
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();

                    }
                }, BackpressureStrategy.LATEST)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE);            //注意此處,暫時先這么設置
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException ignore) {
                        }
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

運行后日志對比如下:
DROP:


DROP.jpg

LATEST:

LATEST.jpg

latest策略下Subscriber在接收完成之前,接收的數據是Flowable發射的最后一條數據,而Drop策略下不是。

BUFFER

Flowable處理背壓的默認策略,對應于BufferAsyncEmitter<T>類
其部分源碼為:

static final class BufferAsyncEmitter<T> extends BaseEmitter<T> {
        private static final long serialVersionUID = 2427151001689639875L;
        final SpscLinkedArrayQueue<T> queue;
        . . . . . .
        final AtomicInteger wip;
        BufferAsyncEmitter(Subscriber<? super T> actual, int capacityHint) {
            super(actual);
            this.queue = new SpscLinkedArrayQueue<T>(capacityHint);
            this.wip = new AtomicInteger();
        }
        . . . . . .
}

在其構造方法中可以發現,其內部維護了一個緩存池SpscLinkedArrayQueue,其大小不限,此策略下,如果Flowable默認的異步緩存池滿了,會通過此緩存池暫存數據,它與Observable的異步緩存池一樣,可以無限制向里添加數據,不會拋出MissingBackpressureException異常,但會導致OOM。
運行如下代碼:

public void demo6() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        int i = 0;
                        while (true) {
                            i++;
                            e.onNext(i);
                        }
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        Thread.sleep(5000);
                        System.out.println(integer);
                    }
                });
    }

查看內存使用:

GIF222.gif

會發現和使用Observable時一樣,都會導致內存劇增,最后導致OOM,不同的是使用Flowable內存增長的速度要慢得多,那是因為基于Flowable發射的數據流,以及對數據加工處理的各操作符都添加了背壓支持,附加了額外的邏輯,其運行效率要比Observable低得多。

MISSING

對應于MissingEmitter<T>類,
通過其源碼:

static final class MissingEmitter<T> extends BaseEmitter<T> {


        private static final long serialVersionUID = 3776720187248809713L;

        MissingEmitter(Subscriber<? super T> actual) {
            super(actual);
        }

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

            if (t != null) {
                actual.onNext(t);
            } else {
                onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
                return;
            }

            for (;;) {
                long r = get();
                if (r == 0L || compareAndSet(r, r - 1)) {
                    return;
                }
            }
        }

    }

可以發現,在傳遞數據時

actual.onNext(t);

并沒有對緩存池的狀態進行判斷,所以在此策略下,通過Create方法創建的Flowable相當于沒有指定背壓策略,不會對通過onNext發射的數據做緩存或丟棄處理,需要下游通過背壓操作符(onBackpressureBuffer()/onBackpressureDrop()/onBackpressureLatest())指定背壓策略。

onBackpressureXXX背壓操作符

Flowable除了通過create創建的時候指定背壓策略,也可以在通過其它創建操作符just,fromArray等創建后通過背壓操作符指定背壓策略。
onBackpressureBuffer()對應BackpressureStrategy.BUFFER
onBackpressureDrop()對應BackpressureStrategy.DROP
onBackpressureLatest()對應BackpressureStrategy.LATEST
例如代碼

    public void demo7() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        for (int i = 0; i < 500; i++) {
                            e.onNext(i);
                        }
                        e.onComplete();
                    }
                }, BackpressureStrategy.DROP)
                .observeOn(Schedulers.newThread())
                .subscribeOn(Schedulers.newThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        System.out.println(integer);
                    }
                });
    }

等同于,代碼:

    public void demo8() {
        Flowable.range(0, 500)
                .onBackpressureDrop()
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer integer) throws Exception {
                        System.out.println(integer);
                    }
                });
    }

Subscription

Subscription與Disposable均是觀察者與可觀察對象建立訂閱狀態后回調回來的參數,如同通過Disposable的dispose()方法可以取消Observer與Oberverable的訂閱關系一樣,通過Subscription的cancel()方法也可以取消Subscriber與Flowable的訂閱關系。
不同的是接口Subscription中多了一個方法request(long n),如上面代碼中的:

 s.request(Long.MAX_VALUE);   

此方法的作用是什么呢,去掉這個方法會有什么影響呢?
運行如下代碼:

public void demo9() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        System.out.println("發射----> 1");
                        e.onNext(1);
                        System.out.println("發射----> 2");
                        e.onNext(2);
                        System.out.println("發射----> 3");
                        e.onNext(3);
                        System.out.println("發射----> 完成");
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER) //create方法中多了一個BackpressureStrategy類型的參數
                .subscribeOn(Schedulers.newThread())//為上下游分別指定各自的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        //去掉代碼s.request(Long.MAX_VALUE);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收----> " + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

運行結果如下:

System.out: 發射----> 1
System.out: 發射----> 2
System.out: 發射----> 3
System.out: 發射----> 完成

我們發現Flowable照常發送數據,而Subsriber不再接收數據。
這是因為Flowable在設計的時候,采用了一種新的思路——響應式拉取方式,來設置下游對數據的請求數量,上游可以根據下游的需求量,按需發送數據。
如果不顯示調用request則默認下游的需求量為零,所以運行上面的代碼后,上游Flowable發射的數據不會交給下游Subscriber處理。
運行如下代碼:

public void demo10() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        System.out.println("發射----> 1");
                        e.onNext(1);
                        System.out.println("發射----> 2");
                        e.onNext(2);
                        System.out.println("發射----> 3");
                        e.onNext(3);
                        System.out.println("發射----> 完成");
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER) //create方法中多了一個BackpressureStrategy類型的參數
                .subscribeOn(Schedulers.newThread())//為上下游分別指定各自的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(2);//設置Subscriber的消費能力為2
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收----> " + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收----> 完成");
                    }
                });
    }

運行結果如下:

System.out: 發射----> 1
System.out: 發射----> 2
System.out: 發射----> 3
System.out: 發射----> 完成
System.out: 接收----> 1
System.out: 接收----> 2

我們發現通過s.request(2);設置Subscriber的數據請求量為2條,超出其請求范圍之外的數據則沒有接收。
多次調用request會產生怎樣的結果呢?
運行如下代碼:

public void demo11() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 10; i++) {
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(3);//調用兩次request
                        s.request(4);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

通過Flowable發射10條數據,在onSubscribe(Subscription s) 方法中調用兩次request,運行結果如下:

AB417C9CAC5A4BD98375240B5A5C1D6A.jpg

我們發現Subscriber總共接收了7條數據,是兩次需求累加后的數量。

通過日志我們發現,上游并沒有根據下游的實際需求,發送數據,而是能發送多少,就發送多少,不管下游是否需要。
而且超出下游需求之外的數據,仍然放到了異步緩存池中。這點我們可以通過以下代碼來驗證:

public void demo12() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        for (int i = 1; i < 130; i++) {
                            System.out.println("發射---->" + i);
                            e.onNext(i);
                        }
                        e.onComplete();
                    }
                }, BackpressureStrategy.ERROR)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(1);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println("接收------>" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收------>完成");
                    }
                });
    }

通過Flowable發射130條數據,通過s.request(1)設置下游的數據請求量為1條,設置緩存策略為BackpressureStrategy.ERROR,如果異步緩存池超限,會導致MissingBackpressureException異常。
運行之后,日志如下:

MissingBackpressureException.jpg

久違的異常出現了,所以超出下游需求之外的數據,仍然放到了異步緩存池中,并導致緩存池溢出。

那么上游如何才能按照下游的請求數量發送數據呢,
雖然通過request可以設置下游的請求數量,但是上游并沒有獲取到這個數量,如何獲取呢?
這便需要用到Flowable與Observable的第三點區別,Flowable特有的發射器FlowableEmitter

FlowableEmitter

flowable的發射器FlowableEmitter與observable的發射器ObservableEmitter均繼承自Emitter(Emitter在教程二中已經說過了)
比較兩者源碼可以發現;

public interface ObservableEmitter<T> extends Emitter<T> {

    void setDisposable(Disposable d);

    void setCancellable(Cancellable c);

    boolean isDisposed();
  
    ObservableEmitter<T> serialize();
}

public interface FlowableEmitter<T> extends Emitter<T> {

    void setDisposable(Disposable s);

    void setCancellable(Cancellable c);

    long requested();

    boolean isCancelled();

    FlowableEmitter<T> serialize();
}

接口FlowableEmitter中多了一個方法

long requested();

我們可以通過這個方法來獲取當前未完成的請求數量,
運行下面的代碼,這次我們要先喪失一下原則,雖然我們之前說過同步狀態下不使用Flowable,但是這次我們需要先看一下同步狀態下情況。

public void demo13() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 5; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)//上下游運行在同一線程中
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(3);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

打印日志如下:

4.jpg

通過日志我們發現, 通過e.requested()獲取到的是一個動態的值,會隨著下游已經接收的數據的數量而遞減。
在上面的代碼中,我們沒有指定上下游的線程,上下游運行在同一線程中。
這與我們之前提到的,同步狀態下不使用Flowable相違背。那是因為異步情況下e.requested()的值太復雜,必須通過同步情況過渡一下才能說得明白。
我們在上面代碼的基礎上,給上下游指定獨立的線程,代碼如下

public void demo14() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 5; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())//添加兩行代碼,為上下游分配獨立的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(3);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

運行后日志如下:

log5.jpg

雖然我們指定了下游的數據請求量為3,但是我們在上游獲取未完成請求數量的時候,并不是3,而是128。難道上游有個最小未完成請求數量?只要下游設置的數據請求量小于128,上游獲取到的都是128?
帶著這個疑問,我們試一下當下游的數據請求量為500,大于128時的情況。

public void demo15() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 5; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())//添加兩行代碼,為上下游分配獨立的線程
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(500);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

運行日志如下;

log6.jpg

結果還是128.
其實不論下游通過s.request();設置多少請求量,我們在上游獲取到的初始未完成請求數量都是128。
這是為啥呢?
還記得之前我們說過,Flowable有一個異步緩存池,上游發射的數據,先放到異步緩存池中,再由異步緩存池交給下游。所以上游在發射數據時,首先需要考慮的不是下游的數據請求量,而是緩存池中能不能放得下,否則在緩存池滿的情況下依然會導致數據遺失或者背壓異常。如果緩存池可以放得下,那就發送,至于是否超出了下游的數據需求量,可以在緩存池向下游傳遞數據時,再作判斷,如果未超出,則將緩存池中的數據傳遞給下游,如果超出了,則不傳遞。
如果下游對數據的需求量超過緩存池的大小,而上游能獲取到的最大需求量是128,上游對超出128的需求量是怎么獲取到的呢?
帶著這個疑問,我們運行一下,下面的代碼,上游發送150個數據,下游也需要150個數據。

public void demo16() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        String threadName = Thread.currentThread().getName();
                        for (int i = 1; i <= 150; i++) {
                            System.out.println("當前未完成的請求數量-->" + e.requested());
                            System.out.println(threadName + "發射---->" + i);
                            e.onNext(i);
                        }
                        System.out.println(threadName + "發射數據結束" + System.currentTimeMillis());
                        e.onComplete();
                    }
                }, BackpressureStrategy.BUFFER)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(150);
                    }

                    @Override
                    public void onNext(Integer integer) {
                        System.out.println(Thread.currentThread().getName() + "接收---------->" + integer);
                    }

                    @Override
                    public void onError(Throwable t) {
                        t.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println(Thread.currentThread().getName() + "接收----> 完成");
                    }
                });
    }

截取部分日志如下:

log7.jpg

我們發現通過e.requested()獲取到的上游當前未完成請求數量并不是一直遞減的,在遞減到33時,又回升到了128.而回升的時機正好是在下游接收了96條數據之后。我們之前說過,異步緩存池中的數據并不是向下游發射一條便清理一條,而是每等累積到95條時,清理一次。通過e.requested()獲取到的值,正是在異步緩存池清理數據時,回升的。也就是,異步緩存池每次清理后,有剩余的空間時,都會導致上游未完成請求數量的回升,這樣既不會引發背壓異常,也不會導致數據遺失。
上游在發送數據的時候并不需要考慮下游需不需要,而只需要考慮異步緩存池中是否放得下,放得下便發,放不下便暫停。所以,通過e.requested()獲取到的值,并不是下游真正的數據請求數量,而是異步緩存池中可放入數據的數量。數據放入緩存池中后,再由緩存池按照下游的數據請求量向下傳遞,待到傳遞完的數據累積到95條之后,將其清除,騰出空間存放新的數據。如果下游處理數據緩慢,則緩存池向下游傳遞數據的速度也相應變慢,進而沒有傳遞完的數據可清除,也就沒有足夠的空間存放新的數據,上游通過e.requested()獲取的值也就變成了0,如果此時,再發送數據的話,則會根據BackpressureStrategy背壓策略的不同,拋出MissingBackpressureException異常,或者丟掉這條數據。
所以上游只需要在e.requested()等于0時,暫停發射數據,便可解決背壓問題。

最終方案

下面我們回到最初的問題
運行下面代碼:

public void demo17() {
        Observable
                .create(new ObservableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(ObservableEmitter<Integer> e) throws Exception {
                        int i = 0;
                        while (true) {
                            i++;
                            System.out.println("發射---->" + i);
                            e.onNext(i);
                        }
                    }
                })
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Observer<Integer>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(50);
                            System.out.println("接收------>" + integer);
                        } catch (InterruptedException ignore) {
                        }
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onComplete() {
                        System.out.println("接收------>完成");
                    }
                });
    }

由于下游處理數據的速度(Thread.sleep(50))趕不上上游發射數據的速度,則會導致背壓問題。
運行后查看內存使用如下:

GIF333.gif

內存暴增,很快就會OOM
下面,對其通過Flowable做些改進,讓其既不會產生背壓問題,也不會引起異常或者數據丟失。
代碼如下:

public void demo18() {
        Flowable
                .create(new FlowableOnSubscribe<Integer>() {
                    @Override
                    public void subscribe(FlowableEmitter<Integer> e) throws Exception {
                        int i = 0;
                        while (true) {
                            if (e.requested() == 0) continue;//此處添加代碼,讓flowable按需發送數據
                            System.out.println("發射---->" + i);
                            i++;
                            e.onNext(i);
                        }
                    }
                }, BackpressureStrategy.MISSING)
                .subscribeOn(Schedulers.newThread())
                .observeOn(Schedulers.newThread())
                .subscribe(new Subscriber<Integer>() {
                    private Subscription mSubscription;

                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(1);            //設置初始請求數據量為1
                        mSubscription = s;
                    }

                    @Override
                    public void onNext(Integer integer) {
                        try {
                            Thread.sleep(50);
                            System.out.println("接收------>" + integer);
                            mSubscription.request(1);//每接收到一條數據增加一條請求量
                        } catch (InterruptedException ignore) {
                        }
                    }

                    @Override
                    public void onError(Throwable t) {
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }

下游處理數據的速度Thread.sleep(50)趕不上上游發射數據的速度,
不同的是,我們在下游onNext(Integer integer) 方法中,每接收一條數據增加一條請求量,

mSubscription.request(1)

在上游添加代碼

if(e.requested()==0)continue;

讓上游按需發送數據。
運行后查看內存:

GIF999.gif

內存一直相當的平靜,而且上游嚴格按照下游的需求量發送數據,不會產生MissingBackpressureException異常,或者丟失數據。
上一篇:Rxjava2入門教程四:Scheduler線程調度器
下一篇:Rxjava2入門教程六:Single、Completable、Maybe——簡化版的Observable

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

推薦閱讀更多精彩內容