
在 spring reactor 中模拟耗时操作时,必须避免 `thread.sleep()` 等阻塞调用;应使用 `mono.delay()`、`concatmap` 等响应式原语,在不切换线程、不阻塞事件循环的前提下实现可控延迟。
在响应式编程中,“模拟长时间运行的操作”常被误解为“让当前线程睡一会儿”。但 Thread.sleep() 是典型的阻塞式 I/O 行为,会占用调度器线程(如 parallel 或 elastic),违反 Reactor “无阻塞、高吞吐”的设计原则——BlockHound 会直接报错:Blocking call! java.lang.Thread.sleep()。
真正的非阻塞延迟,本质是将时间推进委托给调度器(Scheduler)管理的定时任务,而非挂起线程。Reactor 提供了多个声明式延迟操作符,其中最契合本场景的是:
- Mono.delay(Duration):返回一个在指定延迟后发出 0L 的 Mono
,本身不执行任何计算,纯异步计时; - concatMap:按序订阅每个 Mono,保证逻辑串行(避免并发干扰时间感知),同时保持非阻塞特性;
- 配合 map 或 handle 可在延迟完成后注入业务逻辑(如格式化日志、转换结果)。
✅ 正确示例(推荐):
@Test
public void simulateLengthyProcessingOperationReactor() {
Flux.range(1, 5000)
.concatMap(this::simulateDelay_NON_blocking) // 每个元素串行处理
.subscribe(
System.out::println,
Throwable::printStackTrace,
() -> System.out.println("✅ All done!")
);
}
public Mono simulateDelay_NON_blocking(Integer input) {
return Mono.delay(Duration.ofMillis(4000)) // ✅ 非阻塞计时:交由 Scheduler 调度
.map(unused -> String.format(
"[%d] on thread [%s] at time [%s]",
input,
Thread.currentThread().getName(),
new Date()
));
} ⚠️ 注意事项:
- ❌ 不要使用 delayElements() 替代单元素处理逻辑:它作用于整个流的发射节奏(即两个元素间间隔),而非每个元素内部的“模拟耗时”,语义不符;
- ❌ 不要在 map 中调用阻塞方法(包括 Thread.sleep()、数据库同步查询、文件读取等),这会直接触发 BlockHound 报警;
- ✅ 若需更复杂逻辑(如 CPU 密集型计算),应使用 publishOn(Schedulers.boundedElastic()) 显式切换到弹性线程池,并确保该操作本身仍是非阻塞的(例如拆分为小步 flatMap + delay);
- ✅ 延迟精度受 JVM 定时器和调度器负载影响,不适用于毫秒级强实时场景,但完全满足模拟、压测、节流等开发需求。
总结:非阻塞延迟 = Mono.delay() + 响应式链式组合(如 concatMap/flatMap)。它不消耗线程、不触发上下文切换、通过事件驱动完成时间等待,是 Reactor 生态中模拟“长耗时但非阻塞”行为的标准实践。










