文章目录
- 一.什么是虚拟线程
- 二.虚拟线程与普通线程的区别
- 1.普通线程
- 2.虚拟线程
- 3. 实际应用中的区别
- 三.上demo对比性能。
- 1.线程池配置
- 2.Service实现
- 3.测试结果
- 四.小结
一.什么是虚拟线程
虚拟线程,也称作轻量级线程,是由JVM直接管理的线程类型,它们区别于传统的操作系统线程(即平台线程)。虚拟线程的创建及切换所消耗的成本极低,这使得我们能够在应用程序中创建海量的线程,而不会带来显著的资源负担增加。这一特性在I/O密集型任务及高并发应用场景中尤其具有优势。
Java 19引入了虚拟线程(Virtual Threads),作为Project Loom项目的一部分,其目的在于简化并发编程的流程并增强高并发应用的性能表现。在Spring Boot应用中采用虚拟线程,可以大幅提升线程管理的效率。本文将指导你如何在Spring Boot中启用并有效利用虚拟线程。
二.虚拟线程与普通线程的区别
在Java编程语言中,线程构成了并发编程的基础单元。随着Java 19版本的发布,虚拟线程(Virtual Threads)被引入,相较于传统的普通线程(也被称为平台线程),虚拟线程展现出了明显的优势。先介绍完概念,等下直接上demo直观感受普通线程与虚拟线程之间的性能差距。
普通线程普通线程,也常被称作平台线程,是由操作系统直接进行管理的线程类型。
1.普通线程
- 重量级:普通线程属于重量级资源,每一个线程都会直接映射到操作系统层面的一个原生线程上,这导致了线程的创建与销毁过程会伴随着相对较大的开销。
- 内存占用:每个普通线程都会被分配一个独立的栈空间(其大小通常介于几百KB至几MB之间),这一特性限制了在同一时间内能够创建的线程总数。
- 上下文切换开销大:鉴于普通线程是由操作系统直接管理的,因此在线程之间进行上下文切换时会带来相对较大的开销,这在一定程度上影响了高并发应用场景下的整体性能。
- 适用场景:普通线程更适用于计算密集型任务即CPU 密集型任务,也就是那些需要长时间运行且任务数量相对较少的场景
2.虚拟线程
虚拟线程是由Java虚拟机(JVM)直接管理的轻量级线程,以下为其核心特性概述。
- 轻量级:虚拟线程具备轻量级的特点,其创建与销毁的开销极低,几乎可以媲美于普通对象的创建过程。这一特性使得在应用程序中大量创建虚拟线程成为可能。
- 内存占用低:虚拟线程的栈空间采用动态分配机制,初始时的内存占用极小,这为在有限的内存资源下创建更多的线程提供了可能。
- 上下文切换开销小:由于虚拟线程是由JVM进行管理的,因此在进行上下文切换时所带来的开销相对较小。JVM能够针对线程的调度进行优化,从而进一步提升并发性能。
- 对阻塞操作友好:当虚拟线程执行阻塞操作(例如I/O操作)时,它不会影响到底层的操作系统线程,这一特性显著提升了资源的利用效率。
- 适用场景:虚拟线程非常适合用于高并发、I/O密集型任务场景,例如需要处理大量并发请求的服务器应用等。
3. 实际应用中的区别
在实际应用中,选择采用普通线程还是虚拟线程,这完全取决于具体的应用场景和实际需求。举例来说:
- Web 服务器:在面对高并发的Web请求处理时,采用虚拟线程能够显著提升服务器的吞吐能力以及响应速度。这是因为虚拟线程能够轻松应对成千上万的并发请求,而不会因线程数量的激增而导致系统资源的枯竭。
- 计算密集型任务:对于计算密集型任务而言,普通线程仍然是一个明智的选择。这类任务往往对CPU资源有着较高的需求,而不会频繁出现因大量I/O操作而导致的线程阻塞情况。
- 混合场景:在一些复杂的混合应用场景中,我们可以灵活地结合使用普通线程和虚拟线程。例如,可以利用普通线程来处理那些需要长时间运行的计算密集型任务,同时借助虚拟线程来处理高并发的I/O密集型任务,以此来实现系统资源的最大化利用。
三.上demo对比性能。
我这里用的jdk22。
1.线程池配置
@Configuration
@EnableAsync
public class ThreadConfig {
/**
* 一个 Java普通线程对应一个操作系统线程,这些线程非常消耗资源。
* 普通线程池定义:16个核心线程(模拟IO密集型任务,每个任务休眠1s),最大线程数50,阻塞队列1000:避免触发拒绝策略
* @return
*/
@Bean(name = "asyncTaskExecutor")
public ThreadPoolTaskExecutor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(16);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
/**
* 虚拟线程是 Java 中最重要的创新之一。 它们是在 Project Loom 中开发的,自 Java 19 作为预览功能以来一直包含在 JDK 中,
* 自 Java 21 作为最终版本 (JEP 444) 以来,它们已包含在 JDK 中。
* @return
*/
@Bean(name = "virtualThreadExecutor")
public ExecutorService virtualThreadExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
2.Service实现
测试逻辑:
通过普通线程池提交1000个任务,每个任务休眠1s 对比 通过虚拟线程池提交1000个任务,每个任务休眠1s 。
每个任务执行完利用CountDownLatch计数器减1,主线程等待所有任务执行完(计数器归零),看普通线程池与虚拟线程分别执行完1000个任务的需要多久。
@Service
public class VirtualThreadServiceImpl implements VirtualThreadService {
// 获取虚拟线程执行器
@Autowired
private ExecutorService virtualThreadExecutor;
// 获取异步任务执行器
@Autowired
private ThreadPoolTaskExecutor asyncTaskExecutor;
/**
* 创建线程数量
*/
private static Integer THREAD_NUM = 1000;
/**
* @throws InterruptedException
*/
@Async("virtualThreadExecutor")
public void testVirtualThreadTask() throws InterruptedException {
Instant start = Instant.now();
CountDownLatch taskLatch = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
virtualThreadExecutor.execute(() -> {
// 模拟耗时任务
try {
Thread.sleep(1000);
System.out.println("虚拟线程任务完成!");
taskLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
taskLatch.await();
Instant end = Instant.now();
long duration = Duration.between(start, end).toMillis();
System.out.println("虚拟线程方法执行时间: " + duration + " 毫秒");
}
@Async("asyncTaskExecutor")
public void testNormalThreadTask() throws InterruptedException {
Instant start = Instant.now();
CountDownLatch taskLatch = new CountDownLatch(THREAD_NUM);
for (int i = 0; i < THREAD_NUM; i++) {
asyncTaskExecutor.execute(() -> {
// 模拟耗时任务
try {
Thread.sleep(1000);
System.out.println("普通线程任务完成!");
taskLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
taskLatch.await();
Instant end = Instant.now();
long duration = Duration.between(start, end).toMillis();
System.out.println("普通线程方法执行时间: " + duration + " 毫秒");
}
}
3.测试结果
-
普通线程执行完1000个任务耗时:67563 毫秒 约等于68s钟。
-
虚拟线程执行完1000个任务耗时:1071 毫秒,约1s。 快了足足68倍!!!
可能小伙伴们会有疑问: -
这样对比维度不太准确,因为上面普通线程池定义:16个核心线程(模拟IO密集型任务,每个任务休眠1s),最大线程数50,阻塞队列1000。这样会造成提交1000个任务的时候基本都进入阻塞队列了,真正干活的只有16个核心线程!!!
-
但是我们从另一个角度来看,我们虚拟线程是由jvm管控的轻量级线程可以完全高效处理过来这1000个任务,而且没有核心线程数、最大线程数、任务队列这一说,是不是也可以说明虚拟线程在处理IO密集型任务的优势。
-
如果我把普通线程池定义:核心线程换成1000(每个任务都有线程执行,不用等待),即每个任务对应一个操作系统线程,让1000个核心线程干活,处理1000个任务(生产不可能这样定义,失去了线程池的意义),执行时间也花了近虚拟线程1倍的时间,也干不过虚拟线程!!!
从以上方面对比是不是也体现了虚拟线程的优势所在:轻量级、高效处理高并发的 I/O 密集型任务、上下文切换小。
四.小结
主要介绍下jdk19的新特性虚拟线程,真是性能优化的又一大利器啊!如果小伙伴们项目里面jdk有紧跟潮流,在合适的业务场景不妨试试吧~