簡介
今天我們要介紹的是Reactor中的多執行緒模型和定時器模型,Reactor之前我們已經介紹過了,它實際上是觀察者模式的延伸。
所以從本質上來說,Reactor是和多執行緒無關的。你可以把它用在多執行緒或者不用在多執行緒。
今天將會給大家介紹一下如何在Reactor中使用多執行緒和定時器模型。
Thread多執行緒
先看一下之前舉的Flux的建立的例子:
Flux<String> flux = Flux.generate(
() -> 0,
(state, sink) -> {
sink.next("3 x " + state + " = " + 3*state);
if (state == 10) sink.complete();
return state + 1;
});
flux.subscribe(System.out::println);
可以看到,不管是Flux generator還是subscriber,他們實際上都是執行在同一個執行緒中的。
如果我們想讓subscribe發生在一個新的執行緒中,我們需要新啟動一個執行緒,然後線上程內部進行subscribe操作。
Mono<String> mono = Mono.just("hello ");
Thread t = new Thread(() -> mono
.map(msg -> msg + "thread ")
.subscribe(v ->
System.out.println(v + Thread.currentThread().getName())
)
);
t.start();
t.join();
上面的例子中,Mono在主執行緒中建立,而subscribe發生在新啟動的Thread中。
Schedule定時器
很多情況下,我們的publisher是需要定時去呼叫一些方法,來產生元素的。Reactor提供了一個新的Schedule類來負責定時任務的生成和管理。
Scheduler是一個介面:
public interface Scheduler extends Disposable
它定義了一些定時器中必須要實現的方法:
比如立即執行的:
Disposable schedule(Runnable task);
延時執行的:
default Disposable schedule(Runnable task, long delay, TimeUnit unit)
和定期執行的:
default Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit)
Schedule有一個工具類叫做Schedules,它提供了多個建立Scheduler的方法,它的本質就是對ExecutorService和ScheduledExecutorService進行封裝,將其做為Supplier來建立Schedule。
簡單點看Schedule就是對ExecutorService的封裝。
Schedulers工具類
Schedulers工具類提供了很多個有用的工具類,我們來詳細介紹一下:
Schedulers.immediate():
提交的Runnable將會立馬在當前執行緒執行。
Schedulers.single():
使用同一個執行緒來執行所有的任務。
Schedulers.boundedElastic():
建立一個可重用的執行緒池,如果執行緒池中的執行緒在長時間內都沒有被使用,那麼將會被回收。boundedElastic會有一個最大的執行緒個數,一般來說是CPU cores x 10。 如果目前沒有可用的worker執行緒,提交的任務將會被放入佇列等待。
Schedulers.parallel():
建立固定個數的工作執行緒,個數和CPU的核數相關。
Schedulers.fromExecutorService(ExecutorService):
從一個現有的執行緒池建立Scheduler。
Schedulers.newXXX:
Schedulers提供了很多new開頭的方法,來建立各種各樣的Scheduler。
我們看一個Schedulers的具體應用,我們可以指定特定的Scheduler來產生元素:
Flux.interval(Duration.ofMillis(300), Schedulers.newSingle("test"))
publishOn 和 subscribeOn
publishOn和subscribeOn主要用來進行切換Scheduler的執行上下文。
先講一個結論,就是在鏈式呼叫中,publishOn可以切換Scheduler,但是subscribeOn並不會起作用。
這是因為真正的publish-subscribe關係只有在subscriber開始subscribe的時候才建立。
下面我們來具體看一下這兩個方法的使用情況:
publishOn
publishOn可以在鏈式呼叫的過程中,進行publish的切換:
@Test
public void usePublishOn() throws InterruptedException {
Scheduler s = Schedulers.newParallel("parallel-scheduler", 4);
final Flux<String> flux = Flux
.range(1, 2)
.map(i -> 10 + i + ":"+ Thread.currentThread())
.publishOn(s)
.map(i -> "value " + i+":"+ Thread.currentThread());
new Thread(() -> flux.subscribe(System.out::println),"ThreadA").start();
System.out.println(Thread.currentThread());
Thread.sleep(5000);
}
上面我們建立了一個名字為parallel-scheduler的scheduler。
然後建立了一個Flux,Flux先做了一個map操作,然後切換執行上下文到parallel-scheduler,最後右執行了一次map操作。
最後,我們採用一個新的執行緒來進行subscribe的輸出。
先看下輸出結果:
Thread[main,5,main]
value 11:Thread[ThreadA,5,main]:Thread[parallel-scheduler-1,5,main]
value 12:Thread[ThreadA,5,main]:Thread[parallel-scheduler-1,5,main]
可以看到,主執行緒的名字是Thread。Subscriber執行緒的名字是ThreadA。
那麼在publishOn之前,map使用的執行緒就是ThreadA。 而在publishOn之後,map使用的執行緒就切換到了parallel-scheduler執行緒池。
subscribeOn
subscribeOn是用來切換Subscriber的執行上下文,不管subscribeOn出現在呼叫鏈的哪個部分,最終都會應用到整個呼叫鏈上。
我們看一個例子:
@Test
public void useSubscribeOn() throws InterruptedException {
Scheduler s = Schedulers.newParallel("parallel-scheduler", 4);
final Flux<String> flux = Flux
.range(1, 2)
.map(i -> 10 + i + ":" + Thread.currentThread())
.subscribeOn(s)
.map(i -> "value " + i + ":"+ Thread.currentThread());
new Thread(() -> flux.subscribe(System.out::println), "ThreadA").start();
Thread.sleep(5000);
}
同樣的,上面的例子中,我們使用了兩個map,然後在兩個map中使用了一個subscribeOn用來切換subscribe執行上下文。
看下輸出結果:
value 11:Thread[parallel-scheduler-1,5,main]:Thread[parallel-scheduler-1,5,main]
value 12:Thread[parallel-scheduler-1,5,main]:Thread[parallel-scheduler-1,5,main]
可以看到,不管哪個map,都是用的是切換過的parallel-scheduler。
本文的例子learn-reactive
本文作者:flydean程式那些事
本文連結:http://www.flydean.com/reactor-thread-scheduler/
本文來源:flydean的部落格
歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!