publishOn 和 subscribeOn
publishOn 和 subscribeOn
。這兩個方法的作用是指定執行 Reactive Streaming
的 Scheduler
(可理解爲線程池), 不過這兩個方法是用的場景是不相同的.
爲何需要指定執行 Scheduler
呢?一個顯而易見的原因是:組成一個反應式流的代碼有快有慢,可能你的下游接口RT
是不同的。如果將這些功能都放在一個線程裏執行,快的就會被慢的影響,所以需要相互隔離。這是這兩個方法應用的最典型的場景。
Scheduler
先介紹 Scheduler
這個概念。在 Reactor
中,Scheduler
用來定義執行調度任務的抽象。可以簡單理解爲線程池,但其實際作用要更多。Scheduler 的實現包括:
-
Schedulers.elastic(): 調度器會動態創建工作線程,線程數無上界
-
Execturos.newCachedThreadPool()
-
當前線程,通過 Schedulers.immediate()方法來創建。
-
單一的可複用的線程,通過 Schedulers.single()方法來創建。
-
使用對並行操作優化的線程池,通過 Schedulers.parallel()方法來創建。其中的線程數量取決於 CPU 的核的數量。該調度器適用於計算密集型的流的處理。
-
使用支持任務調度的調度器,通過 Schedulers.timer()方法來創建。
從已有的 ExecutorService 對象中創建調度器,通過 Schedulers.fromExecutorService()方法來創建。
Mono<Void> fluxToBlockingRepository(Flux<User> flux,
BlockingRepository<User> repository) {
return flux
.publishOn(Schedulers.elastic())
.doOnNext(repository::save)
.then();
}
Flux<User> blockingRepositoryToFlux(BlockingRepository<User> repository) {
return Flux.defer(() -> Flux.fromIterable(repository.findAll()))
.subscribeOn(Schedulers.elastic());
}
這裏的 repository
的類型是BlockingRepository
,通常指的是會導致線程阻塞的IO操作
在第一個例子中,在執行了publishOn(Schedulers.elastic())
之後,repository::save
就會被 Schedulers.elastic()
定義的線程池所執行。
而在第二個例子中,subscribeOn(Schedulers.elastic())
的作用類似。它使得 repository.findAll()
(也包括 Flux.fromIterable
)的執行發生在 Schedulers.elastic()
所定義的線程池中。
從上面的描述看,publishOn
和subscribeOn
的作用類似,那兩者的區別又是什麼?
兩者的區別
簡單說,兩者的區別在於影響範圍。publishOn
影響在其之後的 operator
執行的線程池,而 subscribeOn
則會從源頭影響整個執行過程。所以,publishOn
的影響範圍和它的位置有關,而 subscribeOn
的影響範圍則和位置無關。
看個 publishOn 和 subscribeOn 同時使用的例子
Flux.just("tom")
.map(s -> {
System.out.println("[map] Thread name: " + Thread.currentThread().getName());
return s.concat("@mail.com");
})
.publishOn(Schedulers.newElastic("thread-publishOn"))
.filter(s -> {
System.out.println("[filter] Thread name: " + Thread.currentThread().getName());
return s.startsWith("t");
})
.subscribeOn(Schedulers.newElastic("thread-subscribeOn"))
.subscribe(s -> {
System.out.println("[subscribe] Thread name: " + Thread.currentThread().getName());
System.out.println(s);
});
輸出結果如下:
[map] Thread name: thread-subscribeOn-3
[filter] Thread name: thread-publishOn-4
[subscribe] Thread name: thread-publishOn-4
tom@mail.com
從上面的例子可以看出,subscribeOn
定義在publishOn
之後,但是卻從源頭開始生效。而在 publishOn
執行之後,線程池變更爲 publishOn 所定義的。
實際用途
這裏介紹 publishOn
和 subscribeOn
的一種實際用途,那就是反應式編程和傳統的,會導致線程阻塞的編程技術混用的場景。其實開頭兩個例子已經解釋了這個場景。
在第一個 publishOn 的例子中,repository::save 會導致線程阻塞,爲了避免造成對其它反應式操作的影響,便使用 publishOn 改變其執行線程。
在第二個 subscribeOn 的例子中,repository.findAll() 會導致線程阻塞。但是其是源頭的 publisher,因此不能使用 publishOn 改變其 執行線程。這時就需要使用 subscribeOn,在源頭上修改其執行線程。
這樣,通過 publishOn 和 subscribeOn 就在反應式編程中實現了線程池隔離的目的,一定程度上避免了會導致線程阻塞的程序執行影響到反應式編程的程序執行效率。