RxJava 操作符 on和doOn 线程切换 调度 Schedulers 线程池 MD
Markdown版本笔记 | 我的GitHub首页 | 我的博客 | 我的微信 | 我的邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
RxJava 操作符 on和doOn 线程切换 调度 Schedulers 线程池 MD
目录
RxJava 线程池
正常的流程
切换线程对 on** 方法的影响
指定被观察者发布事件的线程
指定订阅者(观察者)接收事件的线程
线程切换
切换线程对 doOn** 的影响
切换线程的操作放在最上面
向下移动1下
向下移动2下
向下移动3下
别人总结的规律
RxJava 线程池
RxJava中的多线程操作主要是由强大的Scheduler
集合提供的。在RxJava中,我们无法直接访问或操作线程。如果想要使用线程的话,必须要通过内置的Scheduler来实现。如果你需要在特定的线程中执行任务的话,我们就需要此选择恰当的Scheduler,Scheduler接下来会从它的池中获取一个可用的线程,并基于该线程执行任务。
在RxJava框架中有多种类型的Scheduler,但是这里比较有技巧的一点就是为合适的工作选择恰当的Scheduler
。如果你没有选择恰当的Scheduler的话,那么任务就无法最优地运行,所以接下来,我们尝试理解每一个Scheduler。
- Schedulers.io()
这是由
无边界线程池
作为支撑的一个Scheduler,它适用于非CPU密集的I/O工作
,比如访问文件系统、执行网络调用、访问数据库
等等。
这个Scheduler是没有限制的,它的线程池可以按需一直增长。
注意:在使用无边界线程池支撑的Scheduler时,我们要特别小心,因为它有可能会导致线程池无限增长,使系统中出现大量的线程。 - Schedulers.computation()
这个Scheduler用于执行
CPU密集
的工作,比如处理大规模的数据集、图像处理
等等。它由一个有界的线程池
作为支撑,线程的最大数量就是可用的处理器数量
。
因为这个Scheduler只适用于CPU密集的任务,我们希望限制线程的数量,这样的话,它们不会彼此抢占CPU时间或出现线程饿死
的现象。 - Schedulers.newThread()
这个Scheduler 每次都会创建一个
全新的线程
来完成一组工作。它不会从任何线程池中受益,线程的创建和销毁都是很昂贵的
,所以你需要非常小心,不要衍生出太多的线程,导致服务器系统变慢或出现内存溢出的错误。
理想情况下,你应该很少使用这个Scheduler,它大多用于在一个完全分离的线程中开始一项长时间运行、隔离的一组任务。 - Schedulers.single()
这个Scheduler是RxJava 2新引入的,它的背后只有一个线程作为支撑,只能按照有序的方式执行任务。如果你有一组后台任务要在App的不同地方执行,但是同时只能承受一个任务执行的话,那么这个Scheduler就可以派上用场了。
- Schedulers.from(Executor executor)
我们可以使用它创建自定义的Scheduler,它是由我们自己的
Executor
作为支撑的。在有些场景下,我们希望创建自定义的Scheduler为App执行特定的任务,这些任务可能需要自定义的线程逻辑。
假设,我们想要限制App中并行网络请求的数量,那么我们就可以创建一个自定义的Scheduler,使其具有一个固定线程池大小的Executor:Scheduler.from(Executors.newFixedThreadPool(n))
,然后将其应用到代码中所有网络相关的Observable上。 - AndroidSchedulers.mainThread()
这是一个特殊的Scheduler,它无法在核心RxJava库中使用,要使用它,必须要借助
RxAndroid
扩展库。这个Scheduler对Android App特别有用,它能够在应用的主线程中执行基于UI的任务。
默认情况下,它会在应用主线程关联的looper
中进行任务排队,但是它有一个其他的变种
,允许我们以API的形式使用任意的Looper:AndroidSchedulers.from(Looper looper)
。
正常的流程
正常的流程为:
doOnSubscribe、onSubscribe、【create】、doOnNext、onNext、doOnComplete、onComplete
例如最基础的形式:
Observable.create(emitter -> {
log("【开始create】");
emitter.onNext("救命啊");
emitter.onComplete();
log("【结束create】");
})
.doOnNext(s -> log("【doOnNext】"))
.doOnError(e -> log("【doOnError】"))
.doOnComplete(() -> log("【doOnComplete】"))
.doOnSubscribe(disposable -> log("【doOnSubscribe】"))
.subscribe(o -> log("【onNext】"), e -> log("【onError】"), () -> log("【onComplete】"), d -> log("【onSubscribe】"));
打印方法为:
private void log(String s) {
String date = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS", Locale.getDefault()).format(new Date());
Log.i("bqt", s + date + "," + (Looper.myLooper() == Looper.getMainLooper()));
}
打印结果为:
【doOnSubscribe】2018.09.15 13:38:01 163,true
【onSubscribe】2018.09.15 13:38:01 165,true
【开始create】2018.09.15 13:38:01 166,true
【doOnNext】2018.09.15 13:38:01 167,true
【onNext】2018.09.15 13:38:01 168,true
【doOnComplete】2018.09.15 13:38:01 171,true
【onComplete】2018.09.15 13:38:01 172,true
【结束create】2018.09.15 13:38:01 173,true
将 Observable 换成下面的几种形式后,效果都和上面基本是一样的:
Observable.just("救命啊") //相应的只是没有打印【开始create】和【结束create】
Observable.create(emitter -> {
log("【开始create】");
emitter.onNext("救命啊"); //相应的只是没有【doOnComplete】和【onComplete】
log("【结束create】");
})
Observable.create(emitter -> {
log("【开始create】");
emitter.onNext("救命啊");
emitter.onNext("HELP"); //相应的有两组 **Next,顺序为【doOnNext】【onNext】【doOnNext】【onNext】
emitter.onComplete();
log("【结束create】");
})
Observable.create(emitter -> {
log("【开始create】");
emitter.onNext("救命啊");
emitter.onError(new RuntimeException("game over")); //相应的是【doOnError】和【onError】
log("【结束create】");
})
切换线程对 on** 方法的影响
通过上面的打印结果,我们可以知道,如果没有添加切换线程的操作,则以上所有方法默认都执行在主线程中。
先说一个极端情况,如果我们整个在一个子线程中执行以上逻辑,例如:
new Thread(() -> ... ).start(); //子线程
则以上所有方法都是在子线程中执行的。
下面我们考虑切换线程对【on**】方法的影响。
指定被观察者发布事件的线程
Observable.create(...)
.subscribeOn(Schedulers.io()) //指定被观察者发布事件的线程:
//.observeOn(Schedulers.io()) //如果订阅者接收事件的线程和被观察者发布事件的线程相同,则可以不必指定接收事件的线程
.subscribe(...);
打印结果为:
【onSubscribe】2018.09.15 14:36:44 010,true
【开始create】2018.09.15 14:36:44 011,false
【onNext】2018.09.15 14:36:44 012,false
【onComplete】2018.09.15 14:36:44 015,false
【结束create】2018.09.15 14:36:44 015,false
这种情况下,其线程效果等价于:
Observable.create(emitter -> new Thread(() -> {...}).start()) //将 create 中的所有操作放到了子线程
.subscribe(...);
指定订阅者(观察者)接收事件的线程
Observable.create(...)
//.subscribeOn(AndroidSchedulers.mainThread()) //因为默认就在主线程,所以可以不指定
.observeOn(Schedulers.io()) //指定订阅者(观察者)接收事件的线程
.subscribe(...);
打印结果为:
【onSubscribe】2018.09.15 14:25:58 682,true
【开始create】2018.09.15 14:25:58 697,true
【结束create】2018.09.15 14:25:58 699,true //此案例中,由于【create】和后面几个回调不在一个线程中,因为存在线程竞争的情况,所以【结束create】的回调时机是不确定的,有可能在【onNext】前也可能在【onNext】后,甚至有可能在【onComplete】之后
【onNext】2018.09.15 14:25:58 700,false
【onComplete】2018.09.15 14:25:58 701,false
这种情况下,其线程效果等价于:
Observable.create(emitter -> {
log("【开始create】"); //主线程
new Thread(() -> {
emitter.onNext("救命啊"); //子线程
emitter.onComplete(); //子线程
}).start();
log("【结束create】"); //主线程
})
.subscribe(...);
线程切换
在被观察者发布事件时将线程切换到一个线程,在订阅者接收事件时将线程切换到另一个线程
Observable.create(...)
.subscribeOn(Schedulers.io()) //指定被观察者发布事件的线程:
.observeOn(AndroidSchedulers.mainThread()) //指定订阅者(观察者)接收事件的线程
.subscribe(...);
打印结果为:
【onSubscribe】2018.09.15 14:12:11 711,true
【开始create】2018.09.15 14:12:11 713,false
【结束create】2018.09.15 14:12:11 714,false //此案例中,由于【create】和后面几个回调不在一个线程中,因为存在线程竞争的情况,所以【结束create】的回调时机是不确定的,有可能在【onNext】前也可能在【onNext】后,甚至有可能在【onComplete】之后
【onNext】2018.09.15 14:12:11 723,true
【onComplete】2018.09.15 14:12:11 724,true
这种情况下,其线程效果等价于:
Observable.create(emitter -> new Thread(() -> {
log("【开始create】"); //子线程
runOnUiThread(() -> {
emitter.onNext("救命啊"); //主线程
emitter.onComplete(); //主线程
});
log("【结束create】"); //子线程
}).start())
.subscribe(...);
切换线程对 doOn** 的影响
注意:下面这些没必要去记,也根本不可能记住,只需要知道,这几个 doOn 方法的执行时机和对应的 on 方法相比并不稳定,且这几个 doOn 方法执行时所在线程也并不稳定即可。
切换线程的操作放在最上面
Observable.create(...)
.subscribeOn(Schedulers.io())//指定 subscribe 时所发生的线程,发射事件的线程
.observeOn(AndroidSchedulers.mainThread()) //指定下游 Observer 回调发生的线程,订阅者接收事件的线程
.doOnNext(s -> Log.i("bqt", "【doOnNext】" + currentData() + "," + isMainThread()))
.doOnComplete(() -> Log.i("bqt", "【doOnComplete】 " + currentData() + "," + isMainThread()))
.doOnSubscribe(disposable -> Log.i("bqt", "【doOnSubscribe】" + currentData() + "," + isMainThread()))
.subscribe(...);
注意:下面这些没必要去记,也根本不可能记住,只需要知道,这几个 doOn 方法的执行时机和对应的 on 方法相比并不稳定,且这几个 doOn 方法执行时所在线程也并不稳定即可。
Observable.create(...)
.subscribeOn(Schedulers.io())//指定 subscribe 时所发生的线程,发射事件的线程
.observeOn(AndroidSchedulers.mainThread()) //指定下游 Observer 回调发生的线程,订阅者接收事件的线程
.doOnNext(s -> Log.i("bqt", "【doOnNext】" + currentData() + "," + isMainThread()))
.doOnComplete(() -> Log.i("bqt", "【doOnComplete】 " + currentData() + "," + isMainThread()))
.doOnSubscribe(disposable -> Log.i("bqt", "【doOnSubscribe】" + currentData() + "," + isMainThread()))
.subscribe(...);
打印结果为:
【doOnSubscribe】2018.08.26 18:05:23 799,true
【onSubscribe】2018.08.26 18:05:23 800,true //【没有改变过】
【create】2018.08.26 18:05:23 803,false //【没有改变过】
【doOnNext】2018.08.26 18:05:23 806,true
【onNext】2018.08.26 18:05:23 807,true //【没有改变过】
【doOnComplete】 2018.08.26 18:05:23 807,true
【onComplete】2018.08.26 18:05:23 807,true //【没有改变过】
向下移动1下
Observable.create(...)
.doOnNext(s -> Log.i("bqt", "【doOnNext】" + currentData() + "," + isMainThread()))
.subscribeOn(Schedulers.io())//指定 subscribe 时所发生的线程,发射事件的线程
.observeOn(AndroidSchedulers.mainThread()) //指定下游 Observer 回调发生的线程,订阅者接收事件的线程
.doOnComplete(() -> Log.i("bqt", "【doOnComplete】 " + currentData() + "," + isMainThread()))
.doOnSubscribe(disposable -> Log.i("bqt", "【doOnSubscribe】" + currentData() + "," + isMainThread()))
.subscribe(...);
打印结果为:
【doOnSubscribe】2018.08.26 18:50:32 149,true
【onSubscribe】2018.08.26 18:50:32 150,true //【没有改变过】
【create】2018.08.26 18:50:32 158,false //【没有改变过】
【doOnNext】2018.08.26 18:50:32 158,false
【onNext】2018.08.26 18:50:32 172,true //【没有改变过】
【doOnComplete】 2018.08.26 18:50:32 172,true
【onComplete】2018.08.26 18:50:32 172,true //【没有改变过】
向下移动2下
Observable.create(...)
.doOnNext(s -> Log.i("bqt", "【doOnNext】" + currentData() + "," + isMainThread()))
.doOnComplete(() -> Log.i("bqt", "【doOnComplete】 " + currentData() + "," + isMainThread()))
.subscribeOn(Schedulers.io())//指定 subscribe 时所发生的线程,发射事件的线程
.observeOn(AndroidSchedulers.mainThread()) //指定下游 Observer 回调发生的线程,订阅者接收事件的线程
.doOnSubscribe(disposable -> Log.i("bqt", "【doOnSubscribe】" + currentData() + "," + isMainThread()))
.subscribe(...);
打印结果为:
【doOnSubscribe】2018.08.26 18:58:01 329,true
【onSubscribe】2018.08.26 18:58:01 349,true //【没有改变过】
【create】2018.08.26 18:58:01 359,false //【没有改变过】
【doOnNext】2018.08.26 18:58:01 359,false
【doOnComplete】 2018.08.26 18:58:01 360,false //注意顺序已经和之前的不一样了
【onNext】2018.08.26 18:58:01 371,true //【没有改变过】
【onComplete】2018.08.26 18:58:01 372,true //【没有改变过】
向下移动3下
Observable.create(...)
.doOnNext(s -> Log.i("bqt", "【doOnNext】" + currentData() + "," + isMainThread()))
.doOnComplete(() -> Log.i("bqt", "【doOnComplete】 " + currentData() + "," + isMainThread()))
.doOnSubscribe(disposable -> Log.i("bqt", "【doOnSubscribe】" + currentData() + "," + isMainThread()))
.subscribeOn(Schedulers.io())//指定 subscribe 时所发生的线程,发射事件的线程
.observeOn(AndroidSchedulers.mainThread()) //指定下游 Observer 回调发生的线程,订阅者接收事件的线程
.subscribe(...);
打印结果为:
【onSubscribe】2018.08.26 19:01:51 196,true //【没有改变过】
【doOnSubscribe】2018.08.26 19:01:51 200,false//注意顺序已经和之前的不一样了
【create】2018.08.26 19:01:51 201,false //【没有改变过】
【doOnNext】2018.08.26 19:01:51 201,false
【doOnComplete】 2018.08.26 19:01:51 201,false//注意顺序已经和之前的不一样了
【onNext】2018.08.26 19:01:51 227,true //【没有改变过】
【onComplete】2018.08.26 19:01:51 228,true //【没有改变过】
别人总结的规律
- doOnSubscribe()与onStart()相似,均在代码调用时就会回调。但doOnSubscribe()能够通过subscribeOn()操作符改变运行的线程且越在后面运行越早;
- doOnSubscribe()后面紧跟subscribeOn(),那么doOnSubscribe()将于subscribeOn()指定的线程保持一致。假设doOnSubscribe()在subscribeOn()之后,他的运行线程得再看情况分析;
- doOnSubscribe()假设在observeOn()后(注意:observeon()后没有紧接着再调用subcribeon()方法)。那么doOnSubscribe的运行线程就是main线程,与observeon()指定的线程没有关系。
- 假设在observeOn()之前没有调用过subcribeOn()方法,observeOn()之后subscribe面()方法之前调用subcribeOn()方法,那么他会改变整个代码流程中全部调用doOnSubscribe()方法所在的线程。同一时候也会改变observeOn()方法之前全部操作符所在的线程(有个重要前提:不满足第2点的条件,也就是doOnSubscribe()后面没有调用subscribeOn()方法)。
- 假设在observeOn()前后都没有调用过subcribeOn()方法,那么整个代码流程中的doOnSubscribe()运行在main线程,与observeOn()指定的线程无关。同一时候observeOn()之前的操作符也将运行在main线程,observeOn()之后的操作符与observeOn()指定的线程保持一致。
再次强调:上面这些没必要去记,也根本不可能记住,只需要知道,这几个 doOn** 方法的执行时机和对应的 on** 方法相比并不稳定,且这几个 doOn** 方法执行时所在线程也并不稳定即可。
2018-9-15
RxJava 操作符 on和doOn 线程切换 调度 Schedulers 线程池 MD的更多相关文章
- Java多线程-线程的调度(守护线程)
本文转自http://www.cnblogs.com/linjiqin/p/3210004.html 感谢作者 守护线程与普通线程写法上基本没啥区别,调用线程对象的方法setDaemon(true), ...
- Python线程:线程的调度-守护线程
Python线程:线程的调度-守护线程 守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程.在python中建议使用的是thread. ...
- android的子线程切换到主线程
在子线程中,如果想更新UI,必须切换到主线程,方法如下: if (Looper.myLooper() != Looper.getMainLooper()) { // If we finish mark ...
- EventBus 消息的线程切换模型与实现原理
一. 序 EventBus 是一个基于观察者模式的事件订阅/发布框架,利用 EventBus 可以在不同模块之间,实现低耦合的消息通信. EventBus 因为其使用简单且稳定,被广泛应用在一些生产项 ...
- Day035--Python--管道, Manager, 进程池, 线程切换
管道 #创建管道的类: Pipe([duplex]):在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process ...
- Java线程切换(一)
(本文由言念小文原创,转载请注明出处) 一 前言有Android开发经验的同学都清楚,UI的更新必须在主线程中进行,且主线程不能被阻塞,否则系统ANR异常.我们往往做一些数据处理是耗时操作,必须要在 ...
- {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器
Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...
- java 线程实现、线程暂停和终止 、线程联合join、线程基本信息获取和设置、线程优先级
转载地址:速学堂 https://www.sxt.cn/Java_jQuery_in_action/eleven-inheritthread.html 1. 通过继承Thread类实现多线程 继承Th ...
- python全栈开发,Day41(线程概念,线程的特点,进程和线程的关系,线程和python理论知识,线程的创建)
昨日内容回顾 队列 队列:先进先出.数据进程安全 队列实现方式:管道+锁 生产者消费者模型:解决数据供需不平衡 管道 双向通信,数据进程不安全 EOFError: 管道是由操作系统进行引用计数的 必须 ...
随机推荐
- php模板引擎之featherview
在纯php文件中不加php结束符是一个好习惯,php结束符仅用于在php与html混写时标示php代码结束. <? ?>是短标签,<?php ?>是长标签,在php的配置文件( ...
- java 使用grpc步骤
1.配置grpc maven依赖 <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-ne ...
- qq sid qq sid 是什么 qq sid 怎么用
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha ======= qq sid qq sid 是什么 qq sid 怎么用 ===== ...
- (android高仿系列)今日头条 --新闻阅读器 (转载)
非常不错,原文地址:http://blog.csdn.net/vipzjyno1/article/details/26514543
- Hystrix简单介绍
Netflix的Hystrix是一个帮助解决分布式系统交互超时处理和容错的类库,同样拥有保护系统的能力. 服务隔离 服务降级 1.服务隔离 在一个系统中,一个业务通常会依赖多个服务,且这若干个服务的调 ...
- 吴恩达-coursera-机器学习-week3
六.逻辑回归(Logistic Regression) 6.1 分类问题 6.2 假说表示 6.3 判定边界 6.4 代价函数 6.5 简化的成本函数和梯度下降 6.6 高级优化 6.7 多类别分类: ...
- 使用gtest对DLL工程进行单元测试的实践
前言 关于单元测试的重要性.gtest的优缺点等就不说了.之前项目是没有做单元测试的,在VS的解决方案中,只有一个可执行的工程,其他的工程都是以DLL库的形式提供.本文只针对使用VS对DLL库进行单元 ...
- 通过WinAPI播放PCM声音
在Windows平台上,播放PCM声音使用的API通常有如下两种. waveOut and waveIn:传统的音频MMEAPI,也是使用的最多的 xAudio2:C++/COM API,主要针对游戏 ...
- How to tell if a file is an EXE or a DLL?
How to tell if a file is an EXE or a DLL? void DumpFile(LPWSTR filename) { HANDLE hFile = CreateFile ...
- PHP 依赖注入(DI) 和 控制反转(IoC)
要想理解 PHP 依赖注入 和 控制反转 两个概念,就必须搞清楚如下的两个问题: DI —— Dependency Injection 依赖注入 IoC —— Inversion of Control ...