RxJava一个最大的优点就是可以非常方便的切换线程。但如果对RxJava的并发机制不了解的话,在使用RxJava API的时候经常会非常困惑,因为很多API的内部实现都用到了这些机制。
subscribeOn(Scheduler scheduler)
observeOn(Scheduler scheduler)
这两个方法是RxJava最重要的两个指定线程的方法。subscribeOn()表示上游(生产者)所处的线程,observeOn()表示下游(消费者)所处的线程。我们可以通过传递一个Scheduler类型的参数给这两个方法,分别指定上下游所处的线程。在Schedulers(有s)这个工具类中定义了五种Scheduler类型。
static final Scheduler SINGLE;
static final Scheduler COMPUTATION;
static final Scheduler IO;
static final Scheduler TRAMPOLINE;
static final Scheduler NEW_THREAD;
这里我们先不管这五种类型有什么区别,后面会详细说明。我们先来看一个subscribeOn()和observeOn()方法的例子:
//上游部分
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity", Thread.currentThread().getName());
int delay = new Random().nextInt(3000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
在这个例子里面,使用同一个生产者,通过一个map在里面随机sleep 3s以内的时间,然后返回字符串的长度。上游线程传的是subscribeOn(Schedulers.io()),也就是io类型的线程,这里不用关心io线程具体是什么,后面会解释。下游线程传的是observeOn(Schedulers.computation()),也就是computation类型的线程。也不用关心computation类型具体是什么含义,后面会解释。
输出结果如下。
Send from RxCachedThreadScheduler-1
Send from RxCachedThreadScheduler-2
Send from RxCachedThreadScheduler-2
Received2 5 on thread RxComputationThreadPool-1
Send from RxCachedThreadScheduler-1
Received1 5 on thread RxComputationThreadPool-2
Received1 4 on thread RxComputationThreadPool-2
Received2 4 on thread RxComputationThreadPool-1
通过结果我们可以得出下面的结论。
1.io类型线程对应的是RxCachedThreadScheduler这个线程名,并且后面有数字编号。
2.computation对应的是RxComputationThreadPool这个线程名,并且后面有数字编号
3.上游确实被指定为了io类型的线程,下游确实被指定为了computation的线程。
4.上游下游线程是不保证顺序的(多线程本来就不保证顺序),这个和java多线程是一样的。
这个例子只是简单介绍了subscribeOn()和observeOn()的使用。可以用于对生产者和消费者分别指定线程。
当然还有很多细节要讲。
subscribeOn()和observeOn()这两个方法是可写可不写的,可以全不写,或者全写,或者只写一个。现在我们讨论只写subscribeOn()的情况。
//上游部分
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(3000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.io());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
Send from RxCachedThreadScheduler-1
Send from RxCachedThreadScheduler-2
Received2 5 on thread RxCachedThreadScheduler-2
Send from RxCachedThreadScheduler-2
Received1 5 on thread RxCachedThreadScheduler-1
Send from RxCachedThreadScheduler-1
Received1 4 on thread RxCachedThreadScheduler-1
Received2 4 on thread RxCachedThreadScheduler-2
可以看到,如果只指定了subscribeOn()的线程类型,那么 observeOn()的类型将自动变得和subscribeOn()的线程类型一样。这个例子里面我们subscribeOn()传入的类型为io类型,输出中的RxCachedThreadScheduler就是io类型的固定名称。observeOn()的类型也自动变成了io类型,我们并没有指定。
例如下面的例子,我们没有指定subscribeOn()的线程类型,而只指定了observeOn()线程为io类型线程。
//上游部分
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(3000);
Thread.sleep(delay);
return s.length();
})
.observeOn(Schedulers.io());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
从结果可以看到,没有指定的subscribeOn()方法默认使用的是main线程。而指定的线程就是使用的所指定的线程类型,这里确实是io类型。
Send from main
Send from main
Received1 5 on thread RxCachedThreadScheduler-1
Received1 4 on thread RxCachedThreadScheduler-1
Send from main
Send from main
Received2 5 on thread RxCachedThreadScheduler-2
Received2 4 on thread RxCachedThreadScheduler-2
从1.2可以猜测,如果两个方法都没有指定的话,很明显都会使用main线程,实际上是使用的调用代码所在的线程。代码和上面是一样的只是没写这两个方法。
//上游部分
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(3000);
Thread.sleep(delay);
return s.length();
});
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
结果和猜测的一样,都是在main线程执行。
Send from main
Received1 5 on thread main
Send from main
Received1 4 on thread main
Send from main
Received2 5 on thread main
Send from main
Received2 4 on thread main
如果重复指定这两个方法中的线程类型或者重复指定一个方法,会发生什么呢?
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(1000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.observeOn(Schedulers.computation());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
从结果可以看到,当有多个subscribeOn的时候,以靠近Observable的那个为准,也就是第一个为准,这样的好处是设计者可以最开始就规定好subscribeOn的类型,而不用担心后面的人会修改。毕竟设计者是最清楚自己封装的意图的,知道适合什么类型的线程。observeOn则相反,后面的会覆盖前面的,实际上这是用户决定的,用户我需求我们无法确定。
Send from RxCachedThreadScheduler-1
Send from RxCachedThreadScheduler-2
Send from RxCachedThreadScheduler-1
Received1 5 on thread RxComputationThreadPool-2
Received1 4 on thread RxComputationThreadPool-2
Send from RxCachedThreadScheduler-2
Received2 5 on thread RxComputationThreadPool-2
Received2 4 on thread RxComputationThreadPool-2
搞清楚subscribeOn()和observeOn()这两个方法的使用细节后,就可以详细介绍RxJava所支持的线程类型了。RxJava的线程类型可以通过Schedulers这个类指定。总共有5种类型,下面一一介绍。
先来说说两个常用的类型。
IO类型通过Schedulers.io()指定。从名字就可以看出,这种类型的线程适合用来处理io密集型的操作,如数据库,网络等。因为IO类型的操作主要时间可能消耗在等待资源上,真正有效的代码时间可能非常的少。相反,另一种类型——computation类型。这种类型叫做计算密集型,主要时间都花费在计算,很少有等待的操作。比如一些图片视频处理,压缩文件等操作,这些操作都是计算密集型操作。
下面我们看下这两种类型的细节。
IO类型
我们举一个IO类型的例子来研究IO类型是细节。
//上游部分
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(3000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received3 " + i + " on thread " +
Thread.currentThread().getName()));
为了说明问题,我们创建了3个观察者。我们指定subscribeOn()和observeOn()都为Schedulers.io()。从下面的结果可以得出如下结论。
1.上游有3个线程,下游有3个线程,一共6个线程。
2.IO线程的名字RxCachedThreadScheduler+数字编号,这里我们只开了三个观察者,有六个线程。实际上,我测试过。你开100个线程也是没有问题的。
3.不保证执行的先后顺序,这个很好理解,因为是多线程,不保证顺序是当然的。
Send from RxCachedThreadScheduler-1
Send from RxCachedThreadScheduler-2
Send from RxCachedThreadScheduler-3
Send from RxCachedThreadScheduler-3
Received3 5 on thread RxCachedThreadScheduler-4
Received3 4 on thread RxCachedThreadScheduler-4
Send from RxCachedThreadScheduler-2
Received2 5 on thread RxCachedThreadScheduler-5
Send from RxCachedThreadScheduler-1
Received1 5 on thread RxCachedThreadScheduler-6
Received2 4 on thread RxCachedThreadScheduler-5
Received1 4 on thread RxCachedThreadScheduler-6
如果再执行一遍上面的代码。你会发现出现了不一样的结果,上游线程RxCachedThreadScheduler后面的数字变成了4,5,6。这实际上是因为io类型的线程是有线程池的,会循环利用。如果你再次执行一遍代码,上游线程又会变成和原来RxCachedThreadScheduler1,2,3一样的情况.
Send from RxCachedThreadScheduler-5
Send from RxCachedThreadScheduler-4
Send from RxCachedThreadScheduler-6
Send from RxCachedThreadScheduler-4
Received2 5 on thread RxCachedThreadScheduler-3
Send from RxCachedThreadScheduler-6
Received3 5 on thread RxCachedThreadScheduler-2
Send from RxCachedThreadScheduler-5
Received1 5 on thread RxCachedThreadScheduler-1
Received1 4 on thread RxCachedThreadScheduler-1
Received3 4 on thread RxCachedThreadScheduler-2
Received2 4 on thread RxCachedThreadScheduler-3
computation类型
我们举一个computation类型的例子来研究computation类型的细节。
//上游部分
Observable<Integer> lengths =
Observable.just("Alpha", "Beta","Epsilon")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(3000);
Thread.sleep(delay);
return s.length();
})
.observeOn(Schedulers.computation());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received3 " + i + " on thread " +
Thread.currentThread().getName()));
注意这个例子的变化,上游部分,我们的数据源从两个变成了三个。观察者数量也从两个变成三个。 我们只指定了observeOn的线程类型为computation。从下面的结果可以看出,和IO明显不同的是,我们写了三个观察者,但并没有创建三个线程,而是只有两个,这是和computation这个类型的特性有关的。computation创建线程的数量是有限的。具体数量取决于CPU的核数,可以通过 **Runtime.getRuntime().availableProcessors();**获取。
从数据的特点可以看出,这些线程是被循环利用的,比如发射第一组数据源的时候,用的都是RxComputationThreadPool-1,发射第二组的时候用的都是RxComputationThreadPool-2。到了第三组又变成RxComputationThreadPool-1了。你可以创建更多数量的观察者来验证这个结论。因为处于清晰的考虑,这里以三个为例子。
Send from main
Send from main
Received1 5 on thread RxComputationThreadPool-1
Send from main
Received1 4 on thread RxComputationThreadPool-1
Received1 7 on thread RxComputationThreadPool-1
Send from main
Send from main
Received2 5 on thread RxComputationThreadPool-2
Send from main
Received2 4 on thread RxComputationThreadPool-2
Received2 7 on thread RxComputationThreadPool-2
Send from main
Send from main
Received3 5 on thread RxComputationThreadPool-1
Send from main
Received3 4 on thread RxComputationThreadPool-1
Received3 7 on thread RxComputationThreadPool-1
下面我们看一个和上面非常类似的情况。上面的例子只写了observeOn(Schedulers.computation())是为了更清楚的说明问题。如果加上subscribeOn(Schedulers.computation())情况又有点不同。在上面代码的基础上加上
subscribeOn(Schedulers.computation())
//上游部分
Observable<Integer> lengths =
Observable.just("Alpha", "Beta","Epsilon")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(1000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.computation());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received3 " + i + " on thread " +
Thread.currentThread().getName()));
观察下面的输出,可以看到两个RxComputationThreadPool线程是交替使用的。也就是上游线程先用RxComputationThreadPool-1,下游线程用RxComputationThreadPool-2,然后不停循环。注意,这里可能存在一个误解,以为上游线程只使用RxComputationThreadPool-1,而下游线程只使用RxComputationThreadPool-2,这是不对的,因为出现这种情况只是巧合——测试的用的CPU只支持两个线程。如果是别的CPU,可能出现3个线程的情况,并且是RxComputationThreadPool-1,2,3按顺序用完后又开始重复。那么上游RxComputationThreadPool-1,2,3的情况都可能出现。下游也是一样。
Send from RxComputationThreadPool-1
Send from RxComputationThreadPool-1
Received1 5 on thread RxComputationThreadPool-2
Send from RxComputationThreadPool-1
Received1 4 on thread RxComputationThreadPool-2
Received1 7 on thread RxComputationThreadPool-2
Send from RxComputationThreadPool-1
Send from RxComputationThreadPool-1
Received2 5 on thread RxComputationThreadPool-2
Send from RxComputationThreadPool-1
Received2 4 on thread RxComputationThreadPool-2
Send from RxComputationThreadPool-1
Received2 7 on thread RxComputationThreadPool-2
Send from RxComputationThreadPool-1
Received3 5 on thread RxComputationThreadPool-2
Send from RxComputationThreadPool-1
Received3 4 on thread RxComputationThreadPool-2
Received3 7 on thread RxComputationThreadPool-2
这个类型和io类型是非常相像的,只是io类型可以会重复利用已启用的线程。newThread类型是没有线程池的。和我们经常写的下面的代码是类似的。在实际项目中这种写法是不太好的,因为通常情况是,一个页面会出现非常多的子线程来异步执行任务,这种情况下线程池是非常有必要的,所以基本都会用io类型来替代newThread类型。
new Thread(new Runable(){
public void run(){
}
})
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(1000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received3 " + i + " on thread " +
Thread.currentThread().getName()));
Send from RxNewThreadScheduler-1
Send from RxNewThreadScheduler-2
Send from RxNewThreadScheduler-3
Send from RxNewThreadScheduler-1
Received1 5 on thread RxNewThreadScheduler-4
Send from RxNewThreadScheduler-3
Received3 5 on thread RxNewThreadScheduler-5
Received3 4 on thread RxNewThreadScheduler-5
Received1 4 on thread RxNewThreadScheduler-4
Send from RxNewThreadScheduler-2
Received2 5 on thread RxNewThreadScheduler-6
Received2 4 on thread RxNewThreadScheduler-6
single类型非常的简单,一直重复用一个线程,类似于单例。
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(1000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.single())
.observeOn(Schedulers.single());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received3 " + i + " on thread " +
Thread.currentThread().getName()));
可以看出都是用的RxSingleScheduler-1这个名字,并且并不会出现交错的情况。
Send from RxSingleScheduler-1
Send from RxSingleScheduler-1
Send from RxSingleScheduler-1
Send from RxSingleScheduler-1
Send from RxSingleScheduler-1
Send from RxSingleScheduler-1
Received1 5 on thread RxSingleScheduler-1
Received1 4 on thread RxSingleScheduler-1
Received2 5 on thread RxSingleScheduler-1
Received2 4 on thread RxSingleScheduler-1
Received3 5 on thread RxSingleScheduler-1
Received3 4 on thread RxSingleScheduler-1
这个类型如果只是单纯的在 subscribeOn和observeOn里面调用的话,和不添加是没有什么区别的。但是,这个类型的真正用法并不是这样。
Observable<Integer> lengths =
Observable.just("Alpha", "Beta")
//随机sleep 3s以内的时间
.map(s -> {
Log.d("ConcurrencyActivity","Send from " +Thread.currentThread().getName());
int delay = new Random().nextInt(1000);
Thread.sleep(delay);
return s.length();
})
.subscribeOn(Schedulers.trampoline())
.observeOn(Schedulers.trampoline());
//下游部分
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received1 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received2 " + i + " on thread " +
Thread.currentThread().getName()));
lengths.subscribe(i ->
Log.d("ConcurrencyActivity", "Received3 " + i + " on thread " +
Thread.currentThread().getName()));
Send from main
Received1 5 on thread main
Send from main
Received1 4 on thread main
Send from main
Received2 5 on thread main
Send from main
Received2 4 on thread main
Send from main
Received3 5 on thread main
Send from main
Received3 4 on thread main
我们看到,这个和不指示subscribeOn和observeOn的结果是一样的。但这并不是这个类型的真正用法。这个类型的正确用途是将嵌套的线程变成顺序的队列。我们见过这样的原生Java线程的嵌套代码:
System.out.println("main start");
new Thread(()->{
System.out.println("out strart");
new Thread(()->{
System.out.println("middle strart");
new Thread(()->{
System.out.println("inner strart");
System.out.println("inner end");
}).start();
System.out.println("middle end");
}).start();
System.out.println("out end");
}).start();
System.out.println("main end");
有多个线程嵌套,执行结果如下:
main start
main end
out strart
out end
middle strart
middle end
inner strart
inner end