Java21的虚拟线程Virtual Thread初体验

我们之前使用的是操作系统平台的线程,就称之为"系统线程"吧。虚拟线程是JDK维护的,原理跟WebFlux的底层实现差不多,都是工作线程分离。

要使用虚拟线程,需要使用JDK21以上,包括21。

虚拟线程可以创建很多很多

系统线程不能轻易创建太多,我们一直被教导创建线程是很重的活动。

java 复制代码
        for (int i = 0; i < 1_000_000; i++) {
            new Thread(() -> {
                longAdder.increment();
                System.out.println(longAdder.longValue());
                try {
                    Thread.sleep(10000);
                } catch (Exception e) {
                    // deal with e
                }
            }).start();
        }

上面尝试创建百万个线程,线程都会休眠不结束。我用了一个LongAdder记录我的笔记本能实际创建多少线程。结果是4000多个,用了6秒:

改成虚拟线程就轻松成功:

java 复制代码
        LongAdder longAdder = new LongAdder();
        for (int i = 0; i < 1_000_000; i++) {
            Thread.ofVirtual().start(() -> {
                longAdder.increment();
                System.out.println(longAdder.longValue());
                try {
                    Thread.sleep(100000);
                } catch (Exception e) {
                    // deal with e
                }
            });
        }

因为虚拟线程很轻量,所以不要使用线程池,可以很轻易的创建很多个。因为能创建很多,所以也不要使用 Thread Local 变量。

IO操作不好阻塞虚拟线程的使用

使用系统线程,必须通过线程池来处理多个任务,不然问题很严重:

java 复制代码
    static void callService(String taskName) {
        try {
            System.out.println(Thread.currentThread() + " executing " + taskName);
            new URL("自己写一个http接口?sleep=2000").getContent();
            System.out.println(Thread.currentThread() + " completed " + taskName);

        } catch (Exception e) {
            // deal with e
        }
    }
	
try (ExecutorService executor = Executors.newFixedThreadPool(5)) {
	for (int i = 0; i <= 10; i++) {
		String taskName = "Task" + i;
		executor.execute(() -> callService(taskName));
	}
}

执行的时候你能看到,线程在执行结束前需要空闲等待任务的IO。毕竟每个任务都是在某一个线程上执行 ------ 说这个干啥?

看一下虚拟线程

java 复制代码
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i <= 600; i++) {
                String taskName = "Task" + i;
                executor.execute(() -> callService(taskName));
            }
        }

这里创建了一个虚拟线程工厂(而不是线程池,记住不要使用虚拟化的线程池),它会给每个任务创建新的虚拟线程。

程序启动后,会立即打印600个"executing",而不是像系统线程那样只打印5个。

为了方便,我们少用几个任务来实验看一下输出:

log 复制代码
VirtualThread[#50]/runnable@ForkJoinPool-1-worker-2 executing Task1
VirtualThread[#48]/runnable@ForkJoinPool-1-worker-1 executing Task0
VirtualThread[#51]/runnable@ForkJoinPool-1-worker-3 executing Task2
VirtualThread[#51]/runnable@ForkJoinPool-1-worker-2 completed Task2
VirtualThread[#48]/runnable@ForkJoinPool-1-worker-3 completed Task0
VirtualThread[#50]/runnable@ForkJoinPool-1-worker-1 completed Task1

仔细看,这里一共3个虚拟线程,因为工厂创建了三个,根据任务数来的。

但是每个任务都是在两个虚拟线程上:Task1 被worker-2接收,却被worker-1完成。

啥时候用

关键问题来了,我们总该使用虚拟线程吗?

对各种问题都通用的答案是:你没遇到问题就别想着解决问题。

如果的确有问题,想看看虚拟线程是否合适,可以看一下任务是否是IO密集型的。

对于计算密集型任务,系统线程比虚拟线程有效得多。

虚拟线程跟WebFlux一样,只能提升系统的吞吐量,并不能加快单个任务的完成时间。

相关推荐
叫致寒吧1 小时前
Tomcat详解
java·tomcat
S***26755 小时前
基于SpringBoot和Leaflet的行政区划地图掩膜效果实战
java·spring boot·后端
马剑威(威哥爱编程)5 小时前
鸿蒙6开发视频播放器的屏幕方向适配问题
java·音视频·harmonyos
JIngJaneIL5 小时前
社区互助|社区交易|基于springboot+vue的社区互助交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·社区互助
V***u4536 小时前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
这是程序猿6 小时前
基于java的ssm框架旅游在线平台
java·开发语言·spring boot·spring·旅游·旅游在线平台
i***t9196 小时前
基于SpringBoot和PostGIS的云南与缅甸的千里边境线实战
java·spring boot·spring
k***08296 小时前
【监控】spring actuator源码速读
java·spring boot·spring
麦麦鸡腿堡7 小时前
Java_网络编程_InetAddress类与Socket类
java·服务器·网络
vx_dmxq2117 小时前
【PHP考研互助系统】(免费领源码+演示录像)|可做计算机毕设Java、Python、PHP、小程序APP、C#、爬虫大数据、单片机、文案
java·spring boot·mysql·考研·微信小程序·小程序·php