调度器Scheduler
一、概述
Scheduler 是 RxJava 中控制线程切换的调度器,负责指定 Observable、Flowable、Single 等操作在哪个线程执行。
常用类型:
| Scheduler | 用途 | 适用场景 |
|---|---|---|
Schedulers.io() |
I/O 密集型操作 | 网络请求、文件读写、数据库操作 |
Schedulers.computation() |
计算密集型操作 | 数据处理、算法计算(默认线程数=CPU核心数) |
Schedulers.newThread() |
每次创建新线程 | 临时异步任务(注意线程开销) |
Schedulers.single() |
单一线程队列执行 | 需要按顺序执行的任务 |
Schedulers.trampoline() |
当前线程队列执行 | 延迟任务到当前线程空闲时执行 |
Schedulers.from(Executor) |
自定义线程池 | 特殊线程池需求 |
重要规则:
- subscribeOn()只生效一次(第一次调用有效,多次调用只有第一次起作用)
- observeOn()可多次调用,每次都会影响后续操作
默认情况下,如果没有指定 Scheduler,操作在调用 - subscribe()的线程执行
二、Scheduler分类
computation
computation Scheduler适用于和CPU有关的任务,但是不适合那些会造成阻塞的任务,如访问磁盘和网络等。这是因为computation Scheduler内部会根据当前运行环境的CPU核心数来创建一个线程池,里面的每个线程会占用一个CPU的核心,从而可以充分地利用CPU的计算资源。如果在computation Scheduler上进行阻塞的操作,当前的Scheduler在阻塞的时候还会占用CPU,从而造成资源的浪费。另外,由于CPU的核心数是有限的,所以computation Scheduler内的线程也是有限的,如果有超出CPU核心数的任务要进行,后来的任务就必须排队等待。所以在使用computation Scheduler时,我们最好也能保证同时进行的任务数量小于CPU的核心数,这样新创建的任务会立刻申请到资源并且开始运行。
当没有使用Scheduler的时候,有很多和时间有关的操作符,如delay、timer、skip、take等,其所创建的Observable默认就是运行在computation Scheduler上的。
newThread
newThread Scheduler每次都会新建一个线程。一般情况下不是很推荐使用这个Scheduler,这是因为每次新建一个线程都会造成稍微的延迟,而且这个线程在任务结束的时候就会终结,所以也不能重用。newThread Scheduler适合那些工作时间长并且总数少的任务,大多数情况下都可以使用io Scheduler来代替newThread Scheduler。.
io
io Scheduler类似于newThread Scheduler,不同之处是io Scheduler的线程可以被回收利用。io Scheduler内部也会维持一个线程池,当使用io Scheduler来处理任务的时候,会首先从线程池中查找空闲的线程,如果有空闲线程就会在这个空闲线程上执行任务;否则就会创建一个新的线程来执行任务,并在任务执行完毕时将这个空闲的线程加入到线程池中。当然空闲的线程不会一直在那里等待,RxJava默认空闲线程的存活时间是60秒,空闲时间超过60秒的线程会被回收。
io Scheduler特别适合那些使用很少CPU资源的I/O操作。因为I/O操作一般都会花费比较长的时间来等待网络请求或者读取磁盘的返回结果,所以使用一个较大的线程池会比较合适,这样新来的任务就不需要排队等待。io Scheduler所使用的线程池是不限大小的,所以如果有足够多的任务同时使用io Scheduler就会导致内存不足(OOM)的错误。
trampoline
trampoline Scheduler同immediate Scheduler很像,都会在当前线程上执行任务。但是trampoline并不是立即开始执行任务的,而是等待当前线程上之前的任务都结束之后再开始执行。同样使用Outer和Inner来分别代表当前线程上的任务和使用trampoline Scheduler的任务,它们的执行顺序如下所示。
from
RxJava内置的各种Scheduler可以满足绝大部分使用需求,但是不排除有一些特殊的需要无法被满足,这时我们可以使用Schedulers.from(Executor executor)工厂方法来根据我们提供的Executor创建Scheduler。
三、线程调度
默认情况下不做任何线程处理,Observable和Observer处于同一线程中。如果想要切换线程,则可以使用subscribeOn(和observeOn()。
3.1、subscribeOn
subscribeOn通过接收一个Scheduler参数,来指定对数据的处理运行在特定的线程调度器Scheduler上。若多次执行subscribeOn,则只有一次起作用。
单击subscribeOn()的源码可以看到,每次调用subscribeOn(0都会创建一个ObservableSubscribeOn对象。

ObservableSubscribeOn真正发生订阅的方法是subscribeActual(Observer<?super T> observer).

其中,SubscribeOnObserver是下游的Observer通过装饰器模式生成的,它实现了Observer、Disposable接口。
接下来,在上游的线程中执行下游Observer的onSubscribe(Disposable disposabel)方法。
然后,将子线程的操作加入Disposable管理中,加入Disposable后可以方便上下游的统一管理。
在这里,已经调用了对应scheduler的scheduleDirect(方法。scheduleDirectO传入的是一个Runnable,也就是下面的SubscribeTask。

SubscribeTask会执行runO对上游的Observable,从而进行订阅。此时,已经在对应的Scheduler线程中运行了。
在RxJava的链式操作中,数据的处理是自下而上的,这点与数据发射正好相反。如果多次调用subscribeOn,则最上面的线程切换最晚执行,所以就变成了只有第一次切换线程才有效。
3.2、observeOn
observeOn同样接收一个Scheduler参数,用来指定下游操作运行在特定的线程调度器Scheduler上。若多次执行observeOn,则每次都起作用,线程会一直切换。
单击observeOn()的源码可以看到,每次调用observeOn()都会创建一个ObservableObserveOn对象。


ObservableObserveOn真正发生订阅的方法是subscribeActual(Observer<?super T>observer)。

如果scheduler是TrampolineScheduler,则上游事件和下游事件会立即产生订阅。
如果不是TrampolineScheduler,.则scheduler会创建自己的Worker,然后上游事件和下游事件产生订阅,生成一个ObserveOnObserver对象,封装了下游真正的Observer。
ObserveOnObserver是ObservableObserveOn的内部类,实现了Observer、Runnable接口。与SubscribeOnObserver不同的是,SubscribeOnObserver实现了Observer、Disposable接口。
在ObserveOnObserver的onNext(0中,schedule0执行了具体调度的方法。


其中,worker是当前scheduler创建的Worker,this指的是当前的ObserveOnObserver对象,this实现了Runnable接口。
然后,再来看看Runnable接口的实现方法runO,这个方法是在Worker对应的线程里执行的。drainNormalO会取出ObserveOnObserver的queue里的数据进行发送。

若下游多次调用observeOn(),则线程会一直切换。每次切换线程,都会把对应的Observer对象的各个方法的处理执行在指定的线程中。
四、使用示例
4.1、单独使用subscribeOn
java
@Test
public void testSubscribeOn() {
Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("hello");
emitter.onNext("world");
}
}).subscribeOn(Schedulers.newThread())
.subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
System.out.println(Thread.currentThread().getName() + " " + s);
}
});
}
RxNewThreadScheduler-1 hello
RxNewThreadScheduler-1 world
此时,所有的操作都是在newThread中运行的,包括发射数据。
4.2、多次切换线程
java
@Test
public void testSwitchThread() {
Observable.just("HELLO WORLD")
.subscribeOn(Schedulers.single())
.map(s -> {
s = s.toLowerCase();
System.out.println(Thread.currentThread().getName() + " map1:" + s);
return s;
})
.observeOn(Schedulers.io())
.map(s -> {
s = s + " tony.";
System.out.println(Thread.currentThread().getName() + " map2:" + s);
return s;
})
.subscribeOn(Schedulers.computation())
.map(s -> {
s = s + "it is a test.";
System.out.println(Thread.currentThread().getName() + " map3:" + s);
return s;
})
.observeOn(Schedulers.newThread())
.subscribe(data -> {
System.out.println(Thread.currentThread().getName() + " subscribe:" + data);
});
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
RxSingleScheduler-1 map1:hello world
RxCachedThreadScheduler-1 map2:hello world tony.
RxCachedThreadScheduler-1 map3:hello world tony.it is a test.
RxNewThreadScheduler-1 subscribe:hello world tony.it is a test.
五、自定义线程池
首先创建一个类实现ThreadFactory接口;然后新建一个Executor对象,设置核心和最大线程池大小为2,将空闲线程的存活时间设置为2秒,使用LinkedBlockingQueue来作为任务排队序列,使用新建的ThreadFactory来创建新的线程;最后根据我们创建的Executor对象获取一个Scheduler对象。有了这个Scheduler对象,我们就可以和使用其他的Scheduler一样来使用它了。
java
class SimpleThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
@Test
public void test() {
Executor executor = new ThreadPoolExecutor(
2,
2,
2000L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1000),
new SimpleThreadFactory()
);
Scheduler scheduler = Schedulers.from(executor);
Observable.interval(1, TimeUnit.SECONDS)
.take(5)
.observeOn(scheduler)
.subscribe(data -> {
System.out.println(Thread.currentThread().getName() + " : " + data);
});
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread-0 : 0
Thread-1 : 1
Thread-0 : 2
Thread-1 : 3
Thread-0 : 4