Reactor Threading and Schedulers线程调度

背景

Reactor是非阻塞响应式编程的一种实现,提供各种异步流操作和编排的API,其本质上是与并发无关的,并不强行使用并发编程,而是提供类库由开发者决定多线程并发模式。

简单实现reactor多线程

根据reactive stream模型可以看出,当调用subscribe时Subscriber和Publisher才建立了发布订阅关系,Subscriber开始请求数据,而在此之前调用链并不会发生数据流动nothing happens until you subscribe,所以默认情况下,可以简单理解subscribe发生的线程即是数据流操作的线程。

如下代码:subscribe在main线程中,整个mono操作都会在main线程中。

Mono.just("hello")
             .map(m -> m + " world")
             .subscribe(s -> System.out.println(printThread(s)));

如下代码:在main函数中声明了mono的doOnNext方法打印线程信息,但这里Mono作为一个Publisher仅仅是声明数据流加工链,直到thread-0线程中subscribe执行后才开始数据链方法调用,所以Mono中方法和subscribe都发生在Thread-0中。

final Mono<String> mono = Mono.just("hello")
        .map(m -> m + " world")
        .doOnNext(m -> System.out.println(printThread(m)));

System.out.println(printThread("hi..."));

Thread thread = new Thread(() -> mono
        .map(m -> m + " !")
        .subscribe(s -> System.out.println(printThread(s))));

thread.start();
thread.join();

Schedulers工具类

reactor提供Scheduler接口相当于reactor中的ExecutorService,其不同的实现定义了不同线程环境,主要方法如下

在Scheduler中,每个Worker都是一个ScheduledExecutorService,或一个包装了ScheduledExecutorService的对象。所以,Scheduler拥有的并不是线程池,而是一个自行维护的ScheduledExecutorService池

Schedulers工具类提供了常用的不同线程池的调度器

  1. 当前线程(Schedulers.immediate());

for (int j = 0; j < 2; j++) {
  Flux.range(j, 2)
    .doOnNext(i -> System.out.println(printThread(i.toString())))
    .subscribeOn(Schedulers.immediate())
    .map(i -> i + "s")
    .subscribe(s -> System.out.println(printThread(s)));
}
System.out.println(printThread("Hi..."));

  

  1. 可重用的单线程(Schedulers.single())。注意,这个方法对所有调用者都提供同一个线程来使用, 直到该调度器被废弃。如果你想使用独占的线程,请使用Schedulers.newSingle();

for (int j = 0; j < 2; j++) {
  Flux.range(j, 2)
    .doOnNext(i -> System.out.println(printThread(i.toString())))
    .subscribeOn(Schedulers.single())
    .map(i -> i + "s")
    .subscribe(s -> System.out.println(printThread(s)));
}
System.out.println(printThread("Hi..."));

  

  1. 弹性线程池(Schedulers.boundedElastic())创建一个可重用的线程池,如果线程池中的线程在长时间内都没有被使用,那么将会被回收。boundedElastic会有一个最大的线程个数,一般来说是CPU cores x 10。 如果目前没有可用的worker线程,提交的任务将会被放入队列等待,对于 I/O 阻塞的场景比较适用。Schedulers.elastic()能够方便地给一个阻塞 的任务分配它自己的线程,从而不会妨碍其他任务和资源;

for (int j = 0; j < 2; j++) {
    Flux.range(j * 10, 2)
            .doOnNext(i -> System.out.println(printThread(i.toString())))
            .subscribeOn(Schedulers.boundedElastic())
            .map(i -> i + "s")
            .subscribe(s -> System.out.println(printThread(s)));
}
System.out.println(printThread("Hi..."));

  1. 固定大小线程池(Schedulers.parallel()),所创建线程池的大小与CPU个数等同;

for (int j = 0; j < 2; j++) {
    Flux.range(j * 10, 2)
            .doOnNext(i -> System.out.println(printThread(i.toString())))
            .subscribeOn(Schedulers.parallel())
            .map(i -> i + "s")
            .subscribe(s -> System.out.println(printThread(s)));
}
System.out.println(printThread("Hi..."));

   将j的值改大后,发现本机cpu最多支持12个线程  

  1. 自定义线程池(Schedulers.fromExecutorService(ExecutorService))基于自定义的ExecutorService创建 Scheduler(虽然不太建议,不过你也可以使用Executor来创建)。

ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int j = 0; j < 3; j++) {
    Flux.range(j * 10, 2)
            .doOnNext(i -> System.out.println(printThread(i.toString())))
            .subscribeOn(Schedulers.fromExecutor(executorService))
            .map(i -> i + "s")
            .subscribe(s -> System.out.println(printThread(s)));
}
System.out.println(printThread("Hi..."));

这里可以看到由于创建的线程池大小是2,而Flux创建了3次,有2次共用的一个线程处理

Schedulers类已经预先创建了几种常用的线程池:使用single()、elastic()和parallel()方法可以分别使用内置的单线程、弹性线程池和固定大小线程池。如果想创建新的线程池,可以使用newSingle()、newElastic()和newParallel()方法。

subscribeOn和publishOn

reactor可以通过publishOn和subscribeOn来切换任务执行线程,如上面的例子中都是通过subscribeOn指定任务执行线程。publishOn会影响链中其后的操作符,subscribeOn无论出现在什么位置,都会影响源头的执行环境,直到切换调度器。

Flux.range(0, 2)
        .map(i -> {
            System.out.println(printThread(i.toString()));
            return i + 1;
        })
        .publishOn(Schedulers.single())
        .filter(i -> {
            System.out.println(printThread(i.toString()));
            return i > 0;
        })
        .publishOn(Schedulers.boundedElastic())
        .flatMap(i -> {
            System.out.println(printThread(i.toString()));
            return Flux.just(i * 10);
        })
        .subscribeOn(Schedulers.parallel())
        .subscribe(i -> System.out.println(printThread(i.toString())));

System.out.println(printThread("Hi..."));

根据执行结果我们可以看出:

  • publishOn会影响链中其后的操作符,比如第一个publishOn调整调度器为single,则filter的处理操作是在single线程池中执行的;同理,flatMap是执行在boundedElastic线程池中的;

  • subscribeOn无论出现在什么位置,都只影响源头的执行环境,也就是range方法是执行在parallel线程池中的,直至被第一个publishOn切换调度器之前,所以range后的map也在parallel线程池中执行;

参考:

Reactor 3 Reference Guide

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值