找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1804

积分

0

好友

233

主题
发表于 12 小时前 | 查看: 0| 回复: 0

什么是虚拟线程

虚拟线程是Java 19开始增加的一个特性,与Golang的协程类似。对于一个早已在其他语言中普及、并且如此实用高效的特性,很多Java开发者早已翘首以盼。

虚拟线程和普通线程的区别

从字面意思理解,“虚拟”线程意味着它并非真正的操作系统线程。它不直接由操作系统内核调度,而是由JVM提供的一层线程抽象,并由平台线程(即普通操作系统线程)进行调度。因此,一个平台线程可以承载成千上万个虚拟线程。

虚拟线程的资源消耗比平台线程小得多。在内存充足的情况下,我们可以轻松创建上百万个虚拟线程,这在Java 19之前是不可想象的。

其实,使用过Akka框架的开发者可能会发现,两者在概念上颇有相似之处。只不过Akka的“Actor”模型是由应用层处理,而虚拟线程是由JVM直接支持,在使用上更为简洁和方便。

SpringBoot使用虚拟线程

接下来,我们将在SpringBoot项目中实践虚拟线程,将其应用于异步任务执行器和HTTP请求处理器,以替换默认的线程池。之后,我们将通过对比测试,直观感受虚拟线程与传统线程在性能上的巨大差异。

配置

本次实践环境为:Java版本 java-20.0.2-oracle,SpringBoot版本 3.1.2

在SpringBoot中启用虚拟线程非常简单,只需添加如下配置类:

/**
 * 该配置用于后续测试,spring.virtual-thread=true时使用虚拟线程,false时使用默认的普通线程
 */
@Configuration
@ConditionalOnProperty(prefix = "spring", name = "virtual-thread", havingValue = "true")
public class ThreadConfig {

    @Bean
    public AsyncTaskExecutor applicationTaskExecutor() {
        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
    }

    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerCustomizer() {
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

@Async性能对比

我们首先编写一个异步Service,其中模拟一个耗时约50ms的IO操作(如数据库或缓存查询):

@Service
public class AsyncService {

    /**
     * @param countDownLatch 用于并发测试的计数器
     */
    @Async
    public void doSomething(CountDownLatch countDownLatch) throws InterruptedException {
        Thread.sleep(50);
        countDownLatch.countDown();
    }
}

然后,我们编写测试方法,循环调用该异步方法10万次,并计算总耗时:

@Test
public void testAsync() throws InterruptedException {
    long start = System.currentTimeMillis();
    int n = 100000;
    CountDownLatch countDownLatch = new CountDownLatch(n);
    for (int i = 0; i < n; i++) {
        asyncService.doSomething(countDownLatch);
    }
    countDownLatch.await();
    long end = System.currentTimeMillis();
    System.out.println("耗时:" + (end - start) + "ms");
}

普通线程耗时:约678秒(超过10分钟)。

普通线程异步测试耗时结果

虚拟线程耗时:约3.9秒!

虚拟线程异步测试耗时结果

可以看到,性能差距接近200倍,这无疑是质的变化。传统线程池在面对海量短期阻塞任务时,多线程的创建和上下文切换开销巨大,而虚拟线程则能轻装上阵。

HTTP请求性能对比

我们再看看在Web场景下的表现。编写一个简单的GET接口,同样模拟50ms的IO延迟:

@RequestMapping("/get")
public Object get() throws Exception {
    Thread.sleep(50);
    return "ok";
}

接下来,我们使用JMeter进行压测:设置500个并发线程,循环执行,总请求数为1万次。

「普通线程表现:」

普通线程HTTP请求响应时间百分位图

从图中可以看到,最小响应时间约为50ms,这符合接口内的睡眠设置。但是,中位数以及90%、95%、99%分位的响应时间都超过了150ms。这是因为平台线程是昂贵的系统资源,SpringBoot内嵌Tomcat的默认最大连接数通常为200。当这200个线程都在“干等”50ms时,后续的请求无法得到处理,只能在队列中排队,从而导致响应时间大幅增加。

「虚拟线程表现:」

虚拟线程HTTP请求响应时间趋势图

切换到虚拟线程后,即使是最大响应时间也保持在100ms以下。这意味着线程等待时间显著减少,系统资源(特别是CPU)得到了更充分的利用,能够以更低的延迟处理更多的并发请求。

总结

通过上述对比测试,虚拟线程在性能上的优势非常明显。但需要特别注意:我们的测试都让线程等待了50ms,这模拟的是什么场景?

没错,正是IO密集型场景。在这种场景下,线程大部分时间在等待网络、磁盘等IO操作,而非执行计算。虚拟线程的优势在于,当某个虚拟线程因IO而阻塞时,它可以立即让出承载它的平台线程,去执行其他就绪的虚拟线程,从而极大地提升了平台的线程利用率。

如果是CPU密集型任务(线程大部分时间在进行计算),虚拟线程带来的提升可能有限。不过,当前绝大多数Web应用都属于IO密集型,充斥着数据库查询、缓存访问、外部API调用等操作。因此,在Spring Boot应用中启用虚拟线程,对于提升系统吞吐量和降低延迟具有显著意义。

最后,虽然很多公司可能仍在使用Java 8,但技术进步的车轮从未停歇。是时候考虑升级,拥抱像虚拟线程这样能带来实质性提升的新特性了。如果你想了解更多关于Java新特性或高并发架构的实践经验,欢迎来云栈社区交流探讨。




上一篇:Go错误处理实战:告别if err,掌握错误链与包装技术
下一篇:基于Plano的智能模型路由方案:为OpenClaw节省80%的LLM调用成本
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-26 16:23 , Processed in 1.755691 second(s), 46 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表