一、什么是响应式编程
传统编程是同步阻塞的:你调一个方法,线程停在那里等结果。
响应式编程是异步非阻塞的:你把任务声明好,交给框架,有结果了再通知你。核心思想是:
数据以流的形式流动,谁需要谁订阅。
Java 里最主流的响应式库是Reactor,Spring WebFlux 就是基于它。
二、两个核心类型
类型 | 元素个数 | 场景 |
| 0 或 1 个 | 单个结果,比如查询、执行图 |
| 0 到 N 个 | 流式数据,比如 SSE、多行输出 |
它们都是惰性的:不订阅就不会执行。
三、惰性执行
Mono<String> mono = Mono.fromCallable(() -> { System.out.println("执行了"); return "结果"; }); // 此时不会打印"执行了" mono.subscribe(); // 订阅后才执行,打印"执行了"为什么惰性:框架可以把多个操作符组合成一条链,最后一次性执行,而不是每步都立即触发。
四、常见操作符
1. map —— 同步转换
Mono.just("abc").map(s -> s.length()); // Mono<Integer>把每个元素同步转换成另一个值。
2. flatMap —— 异步转换
Mono.just("abc").flatMap(s -> Mono.just(s.length())); // Mono<Integer>里面的 Lambda 返回一个Mono,flatMap会自动展平,避免Mono<Mono<T>>。
3. then —— 做完 A 再做 B
doA().then(doB());忽略 A 的结果,A 完成后执行 B。
4. onErrorResume —— 异常兜底
前面任何一步出错,就返回兜底值,不让异常往上抛。
5. flatMapMany —— Mono 转 Flux
Mono<String>.flatMapMany(s -> Flux.just(s));把一个Mono展开成Flux。
五、线程调度
1. subscribeOn —— 指定执行线程池
.subscribeOn(Schedulers.boundedElastic())告诉 Reactor:这段代码在哪个线程池执行。
2. 常见 Schedulers
调度器 | 用途 | 线程数 |
| 当前线程 | 不切换 |
| 单线程 | 1 |
| CPU 密集 | CPU 核数 |
| 阻塞 IO | CPU 核数 × 10 |
3. 为什么要切线程
因为数据库操作、HTTP 调用是阻塞的。如果直接在主线程执行:
主线程被占住,无法处理其他请求。
并发能力急剧下降。
用subscribeOn(boundedElastic())把阻塞操作挪到专用线程池,主线程继续处理其他请求。
六、阻塞 vs 非阻塞
对比项 | 阻塞(JDBC) | 非阻塞(Reactor) |
调用方式 | 同步 | 异步 |
线程行为 | 原地等 | 立即返回 |
返回值 | 结果本身 |
|
线程利用率 | 低 | 高 |
关键点:Mono本身是非阻塞的,但里面可以包装阻塞代码。用subscribeOn把阻塞代码挪走,就能避免拖累主线程。
七、链式调用
响应式代码是链式的,把多个操作串成一条流水线:
step1() .then(step2()) .flatMap(result -> step3(result)) .onErrorResume(e -> fallback()) .flatMapMany(Flux::just);每个操作符返回新的Mono或Flux,可以继续链式操作。
八、和 CompletableFuture 的区别
对比项 |
|
|
惰性 | 否 | 是 |
元素个数 | 1 个 | 0~1(Mono)/ 0~N(Flux) |
组合能力 | 有限 | 强,支持 |
WebFlux 配合 | 需要包装 | 原生支持 |
空值表示 | 无 |
|
九、总结
概念 | 一句话 |
| 异步的 0 或 1 个结果 |
| 异步的 0 到 N 个结果 |
| 同步转换 |
| 异步转换 + 展平 |
| 做完 A 再做 B |
| 异常兜底 |
| Mono 转 Flux |
| 指定执行线程池 |
| 阻塞任务的专用线程池 |
核心思想:
声明式地描述“做什么”,而不是“怎么做”。
把多个异步操作串成一条链,由框架负责调度、组合、异常处理。
阻塞代码用
subscribeOn挪到专用线程池,主线程保持非阻塞。