前言
这一节看一下:Backpressure操作符
1. 正题
上一节,我们说到通过zip操作符,可以把多个上游发送的事件组合起来发送给下游,有一个问题就是:如果水管A发送事件特别快,水管B发送事件特别慢,可能会出现水管A已经发出1000个事件,水管B才发出一个,组合之后A还剩999个,这些事件只能等B发出事件后才可以组合,那么这些事件必须找一个水缸进行保存。如下图所示:
由上图可知:
- 蓝色方框就是zip给我们提供的水缸,用于保存每个水管发出的事件,等两个水缸都有事件之后,就分别从水缸中取事件进行组合,当一个水缸是空的时候就处于等待状态;
- 这个水缸是一个队列,是按顺序保存的;
如果一直往水缸中存数据,会OOM,下边通过示例代码来演示:
/**
* 使用for循环无限的给水缸中存储数据,会导致OOM
*/
public static void demo1(){
// 创建第一个上游:Observable1
Observable observable1 = Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
for (int i = 0; ; i++) {
emitter.onNext(i);
}
}
}).subscribeOn(Schedulers.io()) ; // 让for循环 在子线程中执行
// 创建第二个上游:Observable2
Observable observable2 = Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
emitter.onNext("A");
}
}).subscribeOn(Schedulers.io()) ; // 让onNext()方法 在子线程中执行
Observable.zip(observable1, observable2, new BiFunction() {
@Override
public String apply(Integer integer, String s) throws Exception {
return integer + s;
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() { // 这里是把onNext()、onError()方法分开new(创建),也可以直接new Observer,一次性创建所有回调方法
@Override
public void accept(String s) throws Exception {
Log.e("TAG", "s -> " + s);
}
}, new Consumer() {
@Override
public void accept(Throwable throwable) throws Exception {
Log.e("TAG" , "throwable -> " + throwable) ;
}
});
}
使用for循环往水缸中无限的存储数据,会OOM,如下图:
出现这种情况肯定是我们不想看到的,所以这个时候就可以引出Backpressure。
2. Backpressure
Backpressure是为了控制流量,因为水缸存储能力有限,我们必须从源头去解决问题,这个才是最根本的,既然你发的那么快,并且数据量发的那么大,我就限制不让你发的那么快就ok。
情况一:
我们先从单一的Observable分析,代码如下:
/**
* 通过单一的Observable分析:Backpressure
*/
public static void demo2(){
Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
for (int i = 0; ; i++) {
emitter.onNext(i);
}
}
// 这里建立连接之后,new Consumer只是让实现onNext()方法,不用实现其他方法 ;如果想要实现其他方法,就new Observer实现其他所有方法即可
}).subscribe(new Consumer() {
@Override
public void accept(Integer integer) throws Exception {
Thread.sleep(2000);
Log.e("TAG" , "integer -> " + integer) ;
}
});
}
运行结果如下:
可以看到很平静,因为上游、下游在同一个线程中,也就是说,上游每次调用onNext()方法相当于直接调用 Consumer中的
@Override
public void accept(Integer integer) throws Exception {
Thread.sleep(2000);
Log.e("TAG" , "integer -> " + integer) ;
}
情况二:让demo2()中的for循环切换到子线程中执行,然后切换到主线程中执行new Consumer:
/**
* 让demo2()中的for循环切换到子线程中执行,然后切换到主线程中执行new Consumer:
*/
public static void demo3(){
Observable.create(new ObservableOnSubscribe() {
@Override
public void subscribe(ObservableEmitter emitter) throws Exception {
for (int i = 0; ; i++) {
emitter.onNext(i);
}
}
}).subscribeOn(Schedulers.io()) // 这里让上边的 for循环在子线程中执行
.observeOn(AndroidSchedulers.mainThread()) // 然后切换到主线程中,去执行new Consumer中的 accept()方法
.subscribe(new Consumer() {
@Override
public void accept(Integer integer) throws Exception {
Thread.sleep(2000);
Log.e("TAG" , "integer -> " + integer) ;
}
});
}
运行结果如下:
可以看到,把上游的 for循环放在 子线程中执行,有OOM,内存爆炸。
3. 为什么让上游 for循环 在主线程和子线程中执行相差这么大?
这个涉及到同步、异步问题。
如demo2():当上游和下游在同一个线程中执行,属于同步问题,意思是上游每发射一次事件,必须等待下游处理完之后,才能接着发射下一个事件;
如demo3():给上游添加线程,让上游的for循环在子线程中执行,然后让下游的new Consumer()的 accept()方法在主线程中执行,属于异步问题,这个时候上游发射数据不需要等待下游接收,可以直接发射,因为是异步关系,两个线程不能直接通信,这个时候需要一个水缸,上游可以发送事件到水缸,下游从水缸中取事件。
所以说:如果上游发射事件太快,下游取事件太慢,就会造成水缸迅速装满,然后溢出来,导致OOM;
4. 同步和异步示例图如下
同步示例图:
异步示例图:
从上图可知:
同步和异步区别在于是否有水缸。
源头找到了,只要有水缸,就会造成上游、下游发射事件不平衡问题,下一节就去解决这个不平衡的问题。