🤗 ApiHug × {Postman|Swagger|Api...} = 快↑ 准√ 省↓
- GitHub - apihug/apihug.com: All abou the Apihug
- apihug.com: 有爱,有温度,有质量,有信任
- 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 上静态方法来完成这样的选择。
Schedulers.immediate()
: 当前线程,直接运行Schedulers.single()
, 共享同一个线程, 如果per-call
单独 thread, 使用Schedulers.newSingle()
。- 无限线程
Schedulers.elastic()
; 最好不要用, 替代者:Schedulers.boundedElastic()
- 有限线程池
Schedulers.boundedElastic()
, 和elastic()
不一样, 她利用 一个线程池, 以重复利用空闲线程, 超过空闲界限的(默认 60s)将被销毁, 和elastic()
不一样,线程池是有上限的( 默认 CPU cores x 10), 最多100 000
任务并发量, 超过后将被置入队列,等待空闲资源被释放, 这个对有线程阻塞的I/O 工作有很多帮助。 - 固定大小线程池
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
可看到 filter
, subscribe
都是在 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
#参考
- Reactor 核心概念open in new window
- reactive-streams-jvmopen in new window
- reactive-streamsopen in new window
- Reactor Coreopen in new window
- Flux 详实的流程图open in new window
- Mono 详实的流程图open in new window
- Threading and Schedulersopen in new window
- Schedulers