给初学者的RxJava2.0教程(五)

来源:互联网 发布:linux samba rpm 编辑:程序博客网 时间:2024/06/05 10:20

前言

大家喜闻乐见的Backpressure来啦.

这一节中我们将来学习Backpressure. 我看好多吃瓜群众早已坐不住了, 别急, 我们先来回顾一下上一节讲的Zip.

正题

上一节中我们说到Zip可以将多个上游发送的事件组合起来发送给下游, 那大家有没有想过一个问题, 如果其中一个水管A发送事件特别快, 而另一个水管B 发送事件特别慢, 那就可能出现这种情况, 发得快的水管A 已经发送了1000个事件了, 而发的慢的水管B 才发一个出来, 组合了一个之后水管A 还剩999个事件, 这些事件需要继续等待水管B 发送事件出来组合, 那么这么多的事件是放在哪里的呢? 总有一个地方保存吧? 没错, Zip给我们的每一根水管都弄了一个水缸 , 用来保存这些事件, 用通俗易懂的图片来表示就是:


zip2.png

如图中所示, 其中蓝色的框框就是zip给我们的水缸! 它将每根水管发出的事件保存起来, 等两个水缸都有事件了之后就分别从水缸中取出一个事件来组合, 当其中一个水缸是空的时候就处于等待的状态.

题外话: 大家来分析一下这个水缸有什么特点呢? 它是按顺序保存的, 先进来的事件先取出来, 这个特点是不是很熟悉呀? 没错, 这就是我们熟知的队列, 这个水缸在Zip内部的实现就是用的队列, 感兴趣的可以翻看源码查看.

好了回到正题上来, 这个水缸有大小限制吗? 要是一直往里存会怎样? 我们来看个例子:

Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {        @Override                                                                              public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {               for (int i = 0; ; i++) {   //无限循环发事件                                                                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);                                                             }                                                                                  });

在这个例子中, 我们分别创建了两根水管, 第一根水管用机器指令的执行速度来无限循环发送事件, 第二根水管随便发送点什么, 由于我们没有发送Complete事件, 因此第一根水管会一直发事件到它对应的水缸里去, 我们来看看运行结果是什么样.

运行结果GIF图:


zip2.gif

我勒个草, 内存占用以斜率为1的直线迅速上涨, 几秒钟就300多M , 最终报出了OOM:

zlc.season.rxjava2demo W/art: Throwing OutOfMemoryError "Failed to allocate a 28 byte allocation with4194304 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)

出现这种情况肯定是我们不想看见的, 这里就可以引出我们的Backpressure了, 所谓的Backpressure其实就是为了控制流量, 水缸存储的能力毕竟有限, 因此我们还得从源头去解决问题, 既然你发那么快, 数据量那么大, 那我就想办法不让你发那么快呗.

那么这个源头到底在哪里, 究竟什么时候会出现这种情况, 这里只是说的Zip这一个例子, 其他的地方会出现吗? 带着这个问题我们来探究一下.

我们让事情变得简单一点, 从一个单一的Observable说起.

来看段代码:

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

这段代码很简单, 上游同样无限循环的发送事件, 在下游每次接收事件前延时2秒. 上下游工作在同一个线程里, 来看下运行结果:


peace.gif

哎卧槽, 怎么如此平静, 感觉像是走错了片场.

为什么呢, 因为上下游工作在同一个线程呀骚年们! 这个时候上游每次调用emitter.onNext(i)其实就相当于直接调用了Consumer中的:

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

所以这个时候其实就是上游每延时2秒发送一次. 最终的结果也说明了这一切.

那我们加个线程呢, 改成这样:

Observable.create(new ObservableOnSubscribe<Integer>() {                                @Override                                                                           public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {            for (int i = 0; ; i++) {    //无限循环发事件                                                                 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);                                                       }                                                                               });

这个时候把上游切换到了IO线程中去, 下游到主线程去接收, 来看看运行结果呢:


violence.gif

可以看到, 给上游加了个线程之后, 它就像脱缰的野马一样, 内存又爆掉了.

为什么不加线程和加上线程区别这么大呢, 这就涉及了同步异步的知识了.

当上下游工作在同一个线程中时, 这时候是一个同步的订阅关系, 也就是说上游每发送一个事件必须等到下游接收处理完了以后才能接着发送下一个事件.

当上下游工作在不同的线程中时, 这时候是一个异步的订阅关系, 这个时候上游发送数据不需要等待下游接收, 为什么呢, 因为两个线程并不能直接进行通信, 因此上游发送的事件并不能直接到下游里去, 这个时候就需要一个田螺姑娘来帮助它们俩, 这个田螺姑娘就是我们刚才说的水缸 ! 上游把事件发送到水缸里去, 下游从水缸里取出事件来处理, 因此, 当上游发事件的速度太快, 下游取事件的速度太慢, 水缸就会迅速装满, 然后溢出来, 最后就OOM了.

这两种情况用图片来表示如下:

同步:


同步.png

异步:


异步.png

从图中我们可以看出, 同步和异步的区别仅仅在于是否有水缸.

相信通过这个例子大家对线程之间的通信也有了比较清楚的认知和理解.

源头找到了, 只要有水缸, 就会出现上下游发送事件速度不平衡的情况, 因此当我们以后遇到BackPressure时, 仔细思考一下水缸在哪里, 找到水缸, 你就找到了解决问题的办法.

既然源头找到了, 那么下一节我们就要来学习如何去解决了. 下节见.




原文链接:http://www.jianshu.com/p/0f2d6c2387c9

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 开业第一天不吉利怎么办 美容店开业第一天没人怎么办 淘宝店铺没有人访问怎么办 淘宝店铺没有人问怎么办 淘宝申请退款后店铺关闭怎么办 宝贝详情怎么改不了怎么办 改详情页后被删除宝贝怎么办 淘宝网商贷生意不好还不了怎么办 英国遗失在酒店物品怎么办 班福法则首位是0怎么办 同事能力比你强怎么办 新买的木板床响怎么办 笔记本键盘驱动坏了怎么办 云柜快递超时了怎么办 毕业设计被老师发现抄的怎么办 地板颜色太深了怎么办 皮质鞋子破皮了怎么办 吃了蜘蛛丝会怎么办 南京高二分班不公平怎么办? 高中分班考试没考好怎么办 实木门上的伸缩缝太深怎么办 mac点关机没反应怎么办 被压倒扁的易拉罐怎么办 白色车漏底漆了怎么办 客厅对着卧室门怎么办 老公不上进还懒怎么办 二胡按弦手指分不开怎么办 酷塑做完后疼痛怎么办 冷冻治疗后水泡破了怎么办 冷冻治疗的水泡破了怎么办? 冷冻治疗水泡破了怎么办 脚上冷冻后起泡怎么办 刺猴冷冻后起泡怎么办 隔壁太吵怎么办阴招 楼上有小孩太吵怎么办 捷达小水管睹了怎么办 楼房下水管冻了怎么办 一楼地面很潮湿怎么办 新房子地面有裂缝怎么办 地砖下面的下水管漏水怎么办 速冻饺子冻在一起了怎么办