如何控制java虚拟线程的并发度?

jdk 21中的虚拟线程已经推出好一段时间了,确实很轻量,先来一段示例:

假如有一段提交订单的业务代码:

1     public void submitOrder(Integer orderId) {
2         sleep(1000);
3         System.out.println("order:" + orderId + " is submitted");
4     }
View Code

这里我们用sleep来模拟,每提交1个订单,大致耗时1秒。

如果有10个订单一起下单,顺序执行的话,至少10秒,很容易想到用多线程:

 1     @Test
 2     public void submitOrder1() {
 3         ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("submitOrder-pool-%d").build();
 4         ExecutorService submitOrderPool = new ThreadPoolExecutor(4, 16, 300L, TimeUnit.SECONDS,
 5                 new LinkedBlockingQueue<>(128), threadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());
 6         CompletableFuture<Void>[] futures = new CompletableFuture[10];
 7         StopWatch watch = new StopWatch();
 8         watch.start();
 9         //下10个订单
10         for (int i = 0; i < 10; i++) {
11             final int orderId = i;
12             CompletableFuture<Void> future = CompletableFuture.runAsync(() -> submitOrder(orderId), submitOrderPool);
13             futures[i] = future;
14         }
15         CompletableFuture.allOf(futures).join();
16         watch.stop();
17         System.out.println("Time: " + watch.getTime());
18     }
View Code

这里我们使用了传统的线程池,核心线程数是4,小于10,所以会内部排队,最终执行效果大致如下:

order:0 is submitted
order:3 is submitted
order:1 is submitted
order:2 is submitted
order:4 is submitted
order:6 is submitted
order:7 is submitted
order:5 is submitted
order:8 is submitted
order:9 is submitted
Time: 3035
View Code

明显快多了,但是占用了4个传统的java线程。

 

我们改用虚拟线程来改写:

 1     @Test
 2     public void submitOrder2() {
 3         CompletableFuture<Void>[] futures = new CompletableFuture[10];
 4         StopWatch watch = new StopWatch();
 5         watch.start();
 6         //下10个订单(这里使用虚拟线程)
 7         try (ExecutorService service = Executors.newVirtualThreadPerTaskExecutor()) {
 8             for (int i = 0; i < 10; i++) {
 9                 final int orderId = i;
10                 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> submitOrder(orderId), service);
11                 futures[i] = future;
12             }
13             CompletableFuture.allOf(futures).join();
14         }
15         watch.stop();
16         System.out.println("Time: " + watch.getTime());
17     }
View Code

运行效果:

 1 order:1 is submitted
 2 order:3 is submitted
 3 order:2 is submitted
 4 order:4 is submitted
 5 order:8 is submitted
 6 order:9 is submitted
 7 order:0 is submitted
 8 order:5 is submitted
 9 order:7 is submitted
10 order:6 is submitted
11 Time: 1035
View Code

对原有代码仅做了轻微改动,就能享受到虚拟线程的好处,占用资源更小,整体耗时更低,看上很完美!但真的这么丝滑吗?

 

这里有一个坑:正所谓“成也萧何,败也萧何”,虚拟线程这种轻量性的机制,导致它创建起来成本太低了,完全没有池化的必要,1台机器轻轻松松就可以瞬间创建上万甚至上百成的虚拟线程。相比传统的线程池机制(有coreSize,maxSize,workQueue缓冲,以及各种policy策略兜底),虚拟线程完全没有并发度控制的概念,如果瞬间生成大量的虚拟线程,每个里都是执行db/redis操作,很容易就把db、redis连接池打爆!!!

 

因此,实际生产应用中,强烈建议大家控制虚拟线程的执行并发数!!!

可以参考下面的做法,使用信号量来约束:

 1     @Test
 2     public void submitOrder3() {
 3         CompletableFuture<Void>[] futures = new CompletableFuture[10];
 4         StopWatch watch = new StopWatch();
 5         watch.start();
 6         //下10个订单
 7         try (ExecutorService service = Executors.newVirtualThreadPerTaskExecutor()) {
 8             //限制并发数为4
 9             final Semaphore POOL = new Semaphore(4);
10             for (int i = 0; i < 10; i++) {
11                 final int orderId = i;
12                 CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
13                     try {
14                         //获取信号量(注:信号量用完了,后面的任务就只能等着)
15                         POOL.acquire();
16                         submitOrder(orderId);
17                     } catch (InterruptedException e) {
18                         e.printStackTrace();
19                     } finally {
20                         //执行完后,释放信号量
21                         POOL.release();
22                     }
23                 }, service);
24                 futures[i] = future;
25             }
26             CompletableFuture.allOf(futures).join();
27         }
28         watch.stop();
29         System.out.println("Time: " + watch.getTime());
30     }
View Code
 1 order:2 is submitted
 2 order:1 is submitted
 3 order:0 is submitted
 4 order:3 is submitted
 5 order:4 is submitted
 6 order:5 is submitted
 7 order:8 is submitted
 8 order:9 is submitted
 9 order:7 is submitted
10 order:6 is submitted
11 Time: 3042
View Code

 

参考文档:
https://blog.moyucoding.com/jvm/2023/09/23/ultimate-guide-to-java-virtual-thread

https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-DC4306FC-D6C1-4BCC-AECE-48C32C1A8DAA

https://www.didispace.com/article/java-21-virtaul-threads.html

posted @ 2024-11-18 21:02  菩提树下的杨过  阅读(6)  评论(0编辑  收藏  举报