RxJava 2.0 tutorial for beginners (5)

Keywords: Java REST

If this is not good, you can see the Public Number Series:

RxJava 2.0 Tutorial (1)

http://mp.weixin.qq.com/s/Nr9kxDR2hRWvfqHUdDF-XA

RxJava 2.0 tutorial (2)

https://mp.weixin.qq.com/s/S-sSWOcd408BtIC7Z7yN8g

RxJava 2.0 Tutorial (3)

http://mp.weixin.qq.com/s/5QjSX0OOtWBhjx5oR9DnBg 

RxJava 2.0 tutorial (4)

https://mp.weixin.qq.com/s/o1TvpqKy1OttGJ_HNfkrhw

RxJava 2.0 Tutorial (V)

http://mp.weixin.qq.com/s/_6GvCJ5rTxZOoRjaqei7aw

RxJava 2.0 Tutorial (6)

https://mp.weixin.qq.com/s/x9j8twk3-MsUA08S55FXWw

RxJava 2.0 Tutorial (7)


Drink this bowl of rum!

Preface

Here comes Backpress, which you all like to hear.

In this section, we will learn back pressure in the future. I think many people who eat melon can't sit down. Don't worry. Let's review Zip in the previous section.

Topic

In the last section, we mentioned that Zip can send multiple events from upstream to downstream. Did you ever think about the question if one of the events sent by Pipeline A was very fast? The other pipe B sends events very slowly, which may happen. The fast pipe A has sent 1000 events. But the slow water pipe B only sent out one, after which there were 999 incidents left in the water pipe A. These events need to continue to wait for the events sent by the water pipe B to be combined. Where are so many events? Is there always a place to save them? Yes, Zip has built a water tank for each of our pipes to save these events, which is expressed in a plain picture.


zip2.png

As shown in the figure, the blue box is the water tank zip gave us! It saves the events emitted by each pipe, and when both tanks have an event, it takes one event from the tank to assemble. When one of the tanks is empty, it is in a waiting state.

Off topic: What's the characteristic of this tank? It's stored in sequence, first come out of the event, is this characteristic familiar? Yes, this is the well-known queue. The realization of this tank in Zip is using the queue. If you are interested, you can look at the source code.

Okay, back to the point, is there a limit on the size of the tank? What if you keep it in? Let's take an example.

Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {    
    @Override                                                                          
    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {       
        for (int i = 0; ; i++) {   //Infinite recurrence events                                                    
            emitter.onNext(i);                                                         
        }                                                                              
    }                                                                                  
}).subscribeOn(Schedulers.io());    

Observable<String> observable2 = Observable.create(new ObservableOnSubscribe<String>() {      
    @Override                                                                          
    public void subscribe(ObservableEmitter<String> emitter) throws Exception {        
        emitter.onNext("A");                                                           
    }                                                                                  
}).subscribeOn(Schedulers.io());    

Observable.zip(observable1, observable2, new BiFunction<Integer, String, String>() {                 
    @Override                                                                          
    public String apply(Integer integer, String s) throws Exception {                  
        return integer + s;                                                            
    }                                                                                  
}).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<String>() {                               
    @Override                                                                          
    public void accept(String s) throws Exception {                                    
        Log.d(TAG, s);                                                                 
    }                                                                                  
}, new Consumer<Throwable>() {                                                         
    @Override                                                                          
    public void accept(Throwable throwable) throws Exception {                         
        Log.w(TAG, throwable);                                                         
    }                                                                                  
});

In this example, we created two pipes. The first pipe uses the speed of execution of machine instructions to send events indefinitely, and the second pipe casually sends something, because we did not send Complete events. So the first pipe will go all the way to its corresponding tank. Let's see what the result is.

Running result GIF diagram:


zip2.gif

I pulled a piece of grass. The memory occupancy rose rapidly in a straight line with slope of 1. In a few seconds, it was more than 300 M, and finally reported OOM:

zlc.season.rxjava2demo W/art: Throwing OutOfMemoryError "Failed to allocate a 28 byte allocation with
4194304 free bytes and 8MB until OOM; 
zlc.season.rxjava2demo W/art: "main" prio=5 tid=1 Runnable      
zlc.season.rxjava2demo W/art:   | group="main" sCount=0 dsCount=0 obj=0x75188710 self=0x7fc0efe7ba00   
zlc.season.rxjava2demo W/art:   | sysTid=32686 nice=0 cgrp=default sched=0/0 handle=0x7fc0f37dc200    
zlc.season.rxjava2demo W/art:   | state=R schedstat=( 0 0 0 ) utm=948 stm=120 core=1 HZ=100         
zlc.season.rxjava2demo W/art:   | stack=0x7fff971e8000-0x7fff971ea000 stackSize=8MB         
zlc.season.rxjava2demo W/art:   | held mutexes= "mutator lock"(shared held)    
zlc.season.rxjava2demo W/art:     at java.lang.Integer.valueOf(Integer.java:742)

This must be something we don't want to see. Here we can draw our Back Pressure. The so-called Back Pressure is actually to control traffic. After all, the storage capacity of the tank is limited, so we have to solve the problem from the source. Since you send so fast and the amount of data is so large, then I will try to prevent you from sending so fast.

So where on earth does this origin come from and when does this happen? Here's just Zip as an example. Will it happen anywhere else? Let's explore with this question.

Let's make things a little simpler, starting with a single Observable.

Let's look at the code:

Observable.create(new ObservableOnSubscribe<Integer>() {                         
    @Override                                                                    
    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception { 
        for (int i = 0; ; i++) {   //Infinite recurrence events                                              
            emitter.onNext(i);                                                   
        }                                                                        
    }                                                                            
}).subscribe(new Consumer<Integer>() {                                           
    @Override                                                                    
    public void accept(Integer integer) throws Exception {                       
        Thread.sleep(2000);                                                      
        Log.d(TAG, "" + integer);                                                
    }                                                                            
});

This code is very simple, the upstream same infinite loop send events, downstream each receive event delay 2 seconds. Upstream and downstream work in the same thread. Look at the results of the operation:


peace.gif

Hey, sleeping in the trough, how so calm, I feel like I'm on the wrong set.

Why, because upstream and downstream work on the same thread, yeah, yeah, yeah, yeah, yeah, yeah, yeah, yeah, every time emitter.onNext(i) is called upstream, it's actually equivalent to calling Consumer directly:

   public void accept(Integer integer) throws Exception {                       
        Thread.sleep(2000);                                                      
        Log.d(TAG, "" + integer);                                                
   }

So at this point, it's actually upstream every 2 seconds delay. The final result also shows that.

So let's add a thread and change it to this way:

Observable.create(new ObservableOnSubscribe<Integer>() {                            
    @Override                                                                       
    public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {    
        for (int i = 0; ; i++) {    //Infinite recurrence events                                                     
            emitter.onNext(i);                                                      
        }                                                                           
    }                                                                               
}).subscribeOn(Schedulers.io())                                                    
        .observeOn(AndroidSchedulers.mainThread())                                  
        .subscribe(new Consumer<Integer>() {                                        
            @Override                                                               
            public void accept(Integer integer) throws Exception {                  
                Thread.sleep(2000);                                                 
                Log.d(TAG, "" + integer);                                           
            }                                                                       
        });

At this time, the upstream switch to the IO thread, downstream to the main thread to receive, to see the results of the operation:


violence.gif

As you can see, after adding a thread to the upstream, it's like a runaway wild horse, and its memory explodes again.

Why not add threads and add threads so different, which involves the knowledge of synchronization and asynchrony.

When the upstream and downstream work in the same thread, it is a synchronous subscription relationship. That is to say, every event sent upstream must wait until the downstream receives and processes it before sending the next event.

When upstream and downstream work in different threads, this is an asynchronous subscription relationship. At this time, the upstream sending data does not need to wait for downstream reception. Why, because the two threads can not communicate directly, so the events sent upstream can not go downstream directly. At this time, we need a snail girl to help them. This snail girl is the water tank we just said! The upstream sends the event to the tank and the downstream takes the event out of the tank for processing. Therefore, when the upstream events are too fast and the downstream events are too slow, the tank will fill up quickly, then overflow, and finally OOM.

These two situations are represented by pictures as follows:

Synchronization:


Synchronization. png

Asynchronous:


Asynchronous.png

As we can see from the figure, the difference between synchronization and asynchrony is only whether there is a water cylinder or not.

I believe that through this example, we have a clear understanding of the communication between threads.

Source found, as long as there is a water tank, there will be upstream and downstream send event speed imbalance, so when we later encounter BackPressure, Think carefully where the water tank is, find the water tank, and you will find a way to solve the problem.

Now that the source has been found, we'll learn how to solve it in the next section. See you in the next section.

Okay, this is the end of the course. Next time I will update the rest. I want to add a public number for continuous learning.

Public number: You can't see And A useful public name

Posted by goa103 on Fri, 12 Jul 2019 14:34:50 -0700