Reactor 线程-响应式编程-005

本文详细介绍了Reactor和RxJava中的线程管理机制,包括concurrency-agnostic设计、Flux和Mono的并行处理、Schedulers的不同调度策略,以及publishOn和subscribeOn在控制异步上下文中的作用。作者强调了线程上下文切换在响应式编程中的重要性和其带来的权衡问题。
摘要由CSDN通过智能技术生成

    🤗 ApiHug × {Postman|Swagger|Api...} = 快↑ 准√ 省↓

  1. GitHub - apihug/apihug.com: All abou the Apihug   
  2. apihug.com: 有爱,有温度,有质量,有信任
  3. ApiHug - API design Copilot - IntelliJ IDEs Plugin | Marketplace

Reactor 和 RxJava 设计上一个非常大的考量就是 concurrency-agnostic, 也就是让开发人员决定并行运作的方式, 同时框架也帮你处理好并行。

获取和运作 Flux 和 Mono 不需要制定一个特定的线程, 实际上可以在当前的线程中, 也就是 subscribe() 被触发的线程, 例子:

public class MainThread {
  public static void main(String[] args) throws InterruptedException {
    final 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();
  }
}

可以看到输出: hello thread Thread-0

#Threading 和 Schedulers

在Reactor,执行模式, 什么时候执行是由 Scheduler 控制, Scheduler 类似 ExecutorService, 但是更多的功能设计。

Schedulersopen in new window 上静态方法来完成这样的选择。

  1. Schedulers.immediate(): 当前线程,直接运行
  2. Schedulers.single(), 共享同一个线程, 如果 per-call 单独 thread, 使用 Schedulers.newSingle()
  3. 无限线程 Schedulers.elastic(); 最好不要用, 替代者: Schedulers.boundedElastic()
  4. 有限线程池 Schedulers.boundedElastic(), 和 elastic() 不一样, 她利用 一个线程池, 以重复利用空闲线程, 超过空闲界限的(默认 60s)将被销毁, 和elastic() 不一样,线程池是有上限的( 默认 CPU cores x 10), 最多 100 000 任务并发量, 超过后将被置入队列,等待空闲资源被释放, 这个对有线程阻塞的I/O 工作有很多帮助。
  5. 固定大小线程池 Schedulers.parallel(), 大小和 CPU cores数量匹配。

public static final int DEFAULT_POOL_SIZE =
    Optional.ofNullable(System.getProperty("reactor.schedulers.defaultPoolSize"))
        .map(Integer::parseInt)
        .orElseGet(() -> Runtime.getRuntime().availableProcessors());

通过 ExecutorService, 可以创建自己策略的scheduler: Schedulers.fromExecutorService(ExecutorService)

定制自己的名字: Schedulers.newParallel(yourScheduleName)

Flux 里面的一些函数接口, 也会显示的从 Schedulers 创建调度类, 比如 Flux.interval(Duration.ofMillis(300)) 工厂方法将会创建一个 Flux<Long> 流,时间频率是300ms。

默认是用 Schedulers.parallel(), 当然你也可以传入自己的方式:

Flux.interval(Duration.ofMillis(300), Schedulers.newSingle("test"))

public static Flux<Long> interval(Duration period) {
 return interval(period, Schedulers.parallel());
}

Reactor 提供了两个入口: publishOn & subscribeOn 来控制响应式编程里面 scheduler 上下文的切换控制。

Reactor 整个操作链条上你可以组装任意个 Flux 或者 Mono, 当你触发 subscribe, 整个链条被创建。

首先是一个 Subscriber 链条被创建, 链向第一个 publisher, 这些都是框架层帮你处理, 你看到的只是最外层的 Flux/Mono 和 Subscription;

内部包含操作和流转的 subscribers 才是底层逻辑的驱动者。

#publishOn

publishOn, 作为操作流程的中的一环, 接受来之上游的信号 replay 通过执行 callback 给下游,这个执行的环境通过 publishOn 传递的 scheduler 来设置:

@Test
void given_scheduler_show_case() throws InterruptedException {

  Scheduler s = Schedulers.newParallel("parallel-scheduler", 4);

  final Flux<String> flux =
      Flux.range(1, 11)
          .map(i -> 10 + i)
          .publishOn(s)
          .map(i -> "value " + i + "  " + Thread.currentThread().getName());

  new Thread(() -> flux.subscribe(System.out::println)).start();

  Thread.sleep(1100);
}

#subscribeOn

先给个例子:


@Test
void given_scheduler_show_case3() throws InterruptedException {
  Flux.just("tom")
      .map(
          s -> {
            System.out.println("[map] Thread name: " + Thread.currentThread().getName());
            return s.concat("@mail.com");
          })
      .publishOn(Schedulers.newElastic("thread-publish-On"))
      .filter(
          s -> {
            System.out.println("[filter] Thread name: " + Thread.currentThread().getName());
            return s.startsWith("t");
          })
      .subscribeOn(Schedulers.newElastic("thread-subscribe-On"))
      .subscribe(
          s -> {
            System.out.println("[subscribe] Thread name: " + Thread.currentThread().getName());
            System.out.println("eventually we got:::"+s);
          });

  Thread.sleep(200);
}

输出:

[map] Thread name: thread-subscribe-On-3
[filter] Thread name: thread-publish-On-4
[subscribe] Thread name: thread-publish-On-4
eventually we got:::tom@mail.com

可看到 filtersubscribe 都是在 publishOn 线程上操作, 而 map 在 subscribeOn 线程上操作。

两者的区别在于影响范围。publishOn 影响在其之后的 operator 执行的线程池,而 subscribeOn 则会从源头影响整个执行过程。

所以,publishOn 的影响范围和它的位置有关,而 subscribeOn 的影响范围则和位置无关。

这里介绍 publishOn 和 subscribeOn 的一种实际用途,那就是反应式编程和传统的,会导致线程阻塞的编程技术混用的场景。

如果你需要从源头控制异步的线程上下文 subscribeOn 可以在'事后' 依然可补救, 如果只是 ‘从今往后’, 那么 publishOn 是个更好控制。

通过 publishOn 和 subscribeOn 在反应式编程中实现了线程池隔离的目的,一定程度上避免了会导致线程阻塞的程序执行影响到反应式编程的程序执行效率。

但是只能在一定程度上避免反应式编程代码执行的效率被影响。因为用来隔离的线程池资源终归是有限的, 如果阻塞的任务太多,依然得不到效率的提高!

#结论

此篇进入 reactor 核心概念区,线程的上下文, 从 各种类型的 scheduler 类似我们的 Execturos thread pool 策略。

到后面的 publishOn 和 subscribeOn 对于线程池隔离的设计。 线程上下文切换虽然带来了便捷,但也是个需要不断权衡的过程。

设计一个响应式的程序难度不传统阻塞式的程序难度高出不仅仅是一个量级!

测试项目 Reactor Testopen in new window

#参考

  1. Reactor 核心概念open in new window
  2. reactive-streams-jvmopen in new window
  3. reactive-streamsopen in new window
  4. Reactor Coreopen in new window
  5. Flux 详实的流程图open in new window
  6. Mono 详实的流程图open in new window
  7. Threading and Schedulersopen in new window
  8. Schedulers

我们

api-hug-contact

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值