我们一起来看下面这个场景:十万个待执行任务,每个任务休眠两秒
1. 采用java-21的虚拟线程池来实现
public static void main(String[] args) throws InterruptedException{ExecutorService VIRTUAL_THREAD_POOL = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("Virtual-Thread#", 1).factory());// 定义任务数:十万个int total = 10 * 10000;CountDownLatch cdl = new CountDownLatch(total);long s = System.currentTimeMillis();for (int i = 0; i < total; i++){VIRTUAL_THREAD_POOL.execute(() -> {try{Thread.sleep(2000);}catch (InterruptedException e){e.printStackTrace();}cdl.countDown();});}cdl.await();System.out.println("十万个任务执行完毕,总耗时:" + (System.currentTimeMillis() - s));}
最终输出:
十万个任务执行完毕,总耗时:3632
是的,你没看错,就只花了3.5秒左右,完成了10万个平均需要2秒才能完成的任务!
2. 采用原生线程池的效果
没有对比就没有伤害,我们一起再来看看用原生线程池的效果,大家先不要着急看结果,先猜测下,下面这段代码会发生什么呢?
public static void main(String[] args) throws InterruptedException{// 定义任务数:十万个int total = 10 * 10000;ExecutorService THREAD_POOL = Executors.newFixedThreadPool(total);CountDownLatch cdl = new CountDownLatch(total);long s = System.currentTimeMillis();for (int i = 0; i < total; i++){THREAD_POOL.execute(() -> {try{Thread.sleep(2000);}catch (InterruptedException e){e.printStackTrace();}cdl.countDown();});}cdl.await();System.out.println("十万个任务执行完毕,总耗时:" + (System.currentTimeMillis() - s));}
输出结果:
是的,你没看错,运行近20秒后,直接内存崩溃了,要知道,我本机内存可是32G的物理机啊!!!都扛不住这10万个原生线程的开销
An unrecoverable stack overflow has occurred.
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_STACK_OVERFLOW (0xc00000fd) at pc=0x00007fff92212f83, pid=26344, tid=307540
#
# JRE version: OpenJDK Runtime Environment (22.0+36) (build 22+36-2370)
# Java VM: OpenJDK 64-Bit Server VM (22+36-2370, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, windows-amd64)
# Problematic frame:
# V [jvm.dll+0x3e2f83][24.301s][warning][os,thread] Failed to start thread "Unknown thread" - _beginthreadex failed (EACCES) for attributes: stacksize: default, flags: CREATE_SUSPENDED STACK_SIZE_PARAM_IS.
[24.301s][warning][os,thread] Failed to start the native thread for java.lang.Thread "pool-1-thread-70502"
Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reachedat java.base/java.lang.Thread.start0(Native Method)at java.base/java.lang.Thread.start(Thread.java:1540)at java.base/java.lang.System$2.start(System.java:2582)at java.base/jdk.internal.vm.SharedThreadContainer.start(SharedThreadContainer.java:152)at java.base/java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:953)at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1364)at com.sinhy.virtualthread.TestThreadSpeed2.main(TestThreadSpeed2.java:66)
3. 结论
java-21推崇的虚拟线程,是轻量级线程(类似于 Go 中的 “协程(Goroutine)”),可以减少编写、维护和调度高吞吐量并发应用程序的工作量。
与本地线程不同,虚拟线程并不有操作系统控制,虚拟线程是一个有JVM管理的用户态线程。对比于本地线程的高资源占用,每个虚拟线程只需要几个字节的内存空间。这是的它更适合控制管理大量的用户访问,或者说处理IO密集型任务。
在创建虚拟线程的数量上几乎没有限制,甚至可以创建一百万个,因为虚拟线程并不需要来自内核的系统调用。
在虚拟线程如此轻量化的条件下,线程池不再成为必须品,只需要在需要的时候尽情创建虚拟线程就好。
虚拟线程和传统的本地线程操作完全兼容,例如本地线程变量,同步块,线程中断,等等。