Refer
- Oracle-javase-21-Virtual-Threads
- Bell-Soft-how-to-use-virtual-threads-with-spring-boot
- Java 21-A Deep dive into Virtual Threads
1-Intro
History
JEP_425: Preview 版本 , jdk19
JEP_436: Second Preview , jdk20
JEP_444: Closed , jdk21
注意到:
- 之前的
virtual-threads实现是可以不支持Thread-Local变量的 . 在jdk21之后就不可能了,这是为了更好的兼容性.
Tips
这并不是代表
ThreadLocal和Virtual-Thread一起使用就是合理的, 只是ScopeValue这个特性还没有 release
Goals: 兼容性
- 一般的 协程实现,例如
golangkotlin都是引入了新的 并发编程范式, 例如响应式编程 异步式函数. - 目前
Java的 虚拟线程目标 仅仅是针对 Thread-Per-Request 这种范式. 所以我们目前也应该仅仅在这种范式下使用Virtual-Thread, 其他的范式,继续wait. - 在 Thread-Per-Request 这种范式下 尽可能去减少 线程切换的成本就是 虚拟线程的目标
并发编程范式的小历史
- 对于内核而言,
KSE和线程的关系是1:1的,Java的Thread则是对 内核中 thread 的封装,也是个1:1. - 线程本身的成本虽然没有那么昂贵,但还是有上限的
- 所以,oracle 的人得到了一个结论, Thread-per-request 的这种并发编程范式和 1:1 的线程实现 组合在一起是不合适的, 虽然编程模型简单,但是
scalability不行
所以,第一个解决方法 是换一个 编程范式, 例如 Netty 等等 其他的异步框架. 他们的思路是 Improving Scalability with the asynchronous style .
- 对某一个
Request而言,不是从头到尾都是一个线程在处理. - 在一个
Request处理的过程中,如果要等待IO, 直接把线程返回 线程池, 比如Netty把EventLoop还给EventLoopGroup - 这种编程范式会增加复杂度,需要非常细粒度的操作
Tips
Netty设计更加精妙,IO结束的时候,重新去申请EventLoop的时候一定是Request注册的那一个,这种 无锁并发设计 被称为 封闭模型
第2个方法则是, Jdk21 的 虚拟线程则是希望在 不修改范式的同时增加性能和可伸缩性.
Implications of Virtual threads
- 虚拟线程非常的便宜,往往不需要池化.
Example
@Bean(destroyMethod = "shutdown")
open fun inferTaskVPool(): ExecutorService {
val factory = Thread.ofVirtual().name("infer-task-v-pool-", 1).factory()
return Executors.newThreadPerTaskExecutor(factory)
}