Skip to content

虚拟线程 ​

打开 spring.threads.virtual.enabled=true 之后,DDK 的上下文、锁和定时任务各自是什么语义。下面每一条都有测试对应,不只是「能启动」。

结论 ​

关注点开启虚拟线程后依据
请求里的操作者不变:OperatorContextFilter 在请求开始时设置,结束时在 finally 里清掉;虚拟线程不复用,也就不会把上一个请求的操作者留给下一个Web starter 的过滤器测试
@Async 里的操作者和租户照样跟过去。Spring Boot 把任务执行器换成每个任务一个虚拟线程的 SimpleAsyncTaskExecutor,Tracer starter 注册的 ContextPropagatingTaskDecorator 仍然用在它上面AsyncOperatorPropagationTest;关掉 ddk.tracer.async-propagation.enabled 后操作者就拿不到了
@Async 里的链路照样跟过去,前提是链路由 Observation 开启(Web 请求、消息消费都是)。只用 Tracer.withSpan 手动放进作用域的 span 不会被带过去,平台线程池上也一样同上
聚合锁 @AggregateLock / AggregateLocks互斥不变。Redisson 按线程区分锁的持有者,每个虚拟线程是不同的持有者;加锁、释放必须在同一个线程上,和平台线程一样ConcurrencyRedisIntegrationTest:32 个虚拟线程竞争同一把锁
等锁时被中断0.11 起抛 CancellationException,中断标志保留,操作不执行。之前报的是 AggregateBusyException(「正在被其他操作处理」),会被当成可以重试的冲突同上;商城的进程内库存锁同样处理
一次锁多个聚合不变:按锁名排序后依次加锁,与线程类型无关executeAll 的测试
定时任务会重叠:调度器给每次执行一个新线程,固定频率和 cron 的任务上一次没跑完,下一次就可能开始。平台线程的默认调度器只有一个线程,不会重叠。固定延迟(fixed-delay)的任务仍然串行VirtualThreadSchedulingTest;固定延迟见 Spring SimpleAsyncTaskScheduler 的文档
加了 @SchedulerLock 的定时任务不重叠:锁挡住了同名任务的下一次执行,进程内的锁存储也一样同上
应用关闭时的定时任务调度器停止触发新的执行,但已经开始的执行不会被停下,会继续跑完;任务要能在应用关闭的过程中安全结束Spring SimpleAsyncTaskScheduler 的文档;测试里上一个上下文关闭后,它的任务仍在运行
集成事件的投递顺序失去 spring.task.execution.pool.core-size=1 这个手段:没有线程池了,每次投递一个线程Event Starter 的「顺序」
投影刷新、事件维护、本地投递不受影响:这些后台工作跑在 DDK 自己创建的单线程执行器上,用的是平台线程,不随 spring.threads.virtual.enabled 改变。投影刷新照旧以系统操作者 ddk-projection 加标记里的租户执行RefreshWorker、EventMaintenance、LocalEventDelivery 的实现

需要做的 ​

  • 定时任务要么加锁,要么能并行。 开启虚拟线程前,检查没有 @SchedulerLock 的固定频率和 cron 任务:执行时间可能超过间隔的,加上锁,或者改成固定延迟。
  • 需要按提交顺序投递事件的,不要开虚拟线程,或者让消费方不依赖顺序(Event Starter 推荐后者)。
  • 自己创建的线程和执行器(Thread.ofVirtual()、Executors.newVirtualThreadPerTaskExecutor())不经过 Spring 的装饰器,用 OperatorContext.wrap 包一下任务,链路用 Observation 的 ContextSnapshot 传递。
  • 处理 CancellationException。 应用关闭或任务被取消时,等锁的操作会以它结束;不要把它映射成「请稍后重试」。

JDK 版本 ​

JDK 21 上,线程在 synchronized 块里阻塞会占住载体线程(pinning),并发量大时虚拟线程的优势会被吃掉。DDK 自己的主代码里没有 synchronized,但 JDBC 驱动、连接池、Redisson 等依赖里可能有。JDK 24 起(JEP 491)synchronized 不再占住载体线程。CI 在 JDK 21 和 25 上运行上面的测试;打算在生产上大量使用虚拟线程的,建议用 JDK 25。

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权