虚拟线程
虚拟线程(Virtual Thread)是 JDK 而不是 OS 实现的轻量级线程,由 JVM 调度。许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。它们可以更有效地运行以thread-per-request(每个请求一个线程,就大大降低了线程上下文的切换)的方式编写的服务器应用程序,从而实现更高的吞吐量和更少的硬件浪费。
虚拟线程有几个核心的对象:
- Continuation:译为“续延”,是用户真实任务的包装器,虚拟线程会把任务包装到一个Continuation实例中,当任务需要阻塞挂起的时候,会调用Continuation的yield操作进行阻塞
- Scheduler:译为“调度器”,会把任务提交到一个平台线程池中执行,虚拟线程中维护了一个默认的调度器DEFAULT_SCHEDULER,这是一个 ForkJoinPool 实例,最大线程数默认是系统核心线程数,最大为 256,可以通过 jdk.virtualThreadScheduler.maxPoolSize 进行设置。
- carrier:载体线程(Thread对象),指的是负责执行虚拟线程中任务的平台线程。
- runContinuation:一个Runnable对象,用于在任务运行或继续之前,虚拟线程将装载到当前线程上。当任务完成或完成时,将其卸载。
如何创建虚拟线程
1、使用**Thread.startVirtualThread
**
1
2
3
4
5
6
7
8
9
10
11
12
|
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
Thread.startVirtualThread(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
|
2、使用**Thread.ofVirtual
**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
// 创建不启动
Thread unStarted = Thread.ofVirtual().unstarted(customThread);
unStarted.start();
// 创建直接启动
Thread.ofVirtual().start(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
|
3、使用 ThreadFactory
创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(customThread);
thread.start();
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
|
4、使用Executors.newVirtualThreadPerTaskExecutor
创建
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
|
springboot3.2与虚拟线程
随着springboot3.2发布,在使用jdk21的情况下。可以使用如下配置开启虚拟线程
1
|
spring.threads.virtual.enabled=true
|
我们写了个简单的demo,进行性能测试。结果如预期所料,响应时间和吞吐率都是虚拟线程完胜。
但改为使用数据库以后,发现性能相比于不使用虚拟线程反而下降了。并且随着请求数量增加,发现数据库连接被占满。Connection is not available, request timed out after 906ms
这是为什么呢?
虚拟线程与synchronized
通过阅读官方虚拟线程文档,目前虚拟线程与synchronized关键字的适配尚未完成。如果遇到synchronized,性能反而会下降。
There are two scenarios in which a virtual thread cannot be unmounted during blocking operations because it is pinned to its carrier:
- When it executes code inside a
synchronized
block or method, or
- When it executes a
native
method or a foreign function.
显然,我们的代码中并无此类场景。所以问题应该是在jdbc或者mysql驱动。起初我们以为是hikari的问题,但在报错的时候,我们看到了如下异常。基本上可以确定是mysql驱动的问题
需要将使用synchronized的代码,全部改为使用**ReentrantLock
**来实现。同时我们也看到了mysql和mybatis社区已经有人反馈此问题,至此我们决定暂时不启用虚拟线程。
**UPDATE2024-05-08:**由于我们在持续跟踪此问题,随着mysql-connector-j 9.0版本和mybatis3.5.16版本发布,代码均已按预期修改。在我们未对代码做额外优化的情况下,各方面性能得到了提升!
我们在4c8g的机器上,使用虚拟线程的配置如下。可以达到最高10k并发、40k tps
1
2
3
4
5
6
7
8
9
10
11
12
|
spring.threads.virtual.enabled=true
#set tomcat thread pool
server.tomcat.threads.max=3000
server.tomcat.threads.min-spare=3000
server.tomcat.max-connections=10000
server.tomcat.accept-count=1000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.maximum-pool-size=3000
spring.datasource.hikari.minimum-idle=2000
spring.datasource.hikari.connection-timeout=3000
spring.datasource.hikari.max-lifetime=3600000
spring.datasource.hikari.idle-timeout=1200000
|
总结
1、强制使用**ReentrantLock
**:虚拟线程可以更好的利用cpu,支持了java版本的协程。但在当前版本无法和*synchronized
*关键字很好的适配。在项目中如果需要线程同步的场景,
2、统一使用虚拟线程:在大多数场景可以抛弃掉原有的各种线程池,由jvm为我们做调度。再也不怕因线程太多而导致oom了
3、在会调用到native方法和jni的地方:不应使用虚拟线程
4、虚拟线程适用于长时间的IO密集型任务,而不适用于长时间的CPU密集型任务
5、虚拟线程开启后,应使用较新版本的mybatis和mysql驱动
6、经过测试,虚拟线程和webfulx时吞吐量大致相当。但使用虚拟线程无需额外的学习成本。虚拟线程吞吐量远超普通线程池!
References
虚拟线程与synchronized
虚拟线程
SpringBoot3.2
mybatis-3.5.16
Replace synchronized with ReentrantLock
hikari
淘宝对虚拟线程的研究