如何控制java虚拟线程的并发度?
jdk 21中的虚拟线程已经推出好一段时间了,确实很轻量,先来一段示例:
假如有一段提交订单的业务代码:

1 public void submitOrder(Integer orderId) {
2 sleep(1000);
3 System.out.println("order:" + orderId + " is submitted");
4 }
这里我们用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 }
这里我们使用了传统的线程池,核心线程数是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
明显快多了,但是占用了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 }
运行效果:

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
对原有代码仅做了轻微改动,就能享受到虚拟线程的好处,占用资源更小,整体耗时更低,看上很完美!但。。。真的这么丝滑吗?
这里有一个坑:正所谓“成也萧何,败也萧何”,虚拟线程这种轻量性的机制,导致它创建起来成本太低了,完全没有池化的必要,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 }

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
参考文档:
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
如何控制java虚拟线程的并发度?的更多相关文章
- Java基础-线程与并发1
线程与并发 Thread 基本概念 程序: 一组计算机能识别和执行的指令 ,是静态的代码. 进程: 程序的一次运行活动, 运行中的程序 . 线程: 进程的组成部分,它代表了一条顺序的执行流. 进程线程 ...
- Java 中 ConcurrentHashMap 的并发度是什么?
ConcurrentHashMap 把实际 map 划分成若干部分来实现它的可扩展性和线程安 全.这种划分是使用并发度获得的,它是 ConcurrentHashMap 类构造函数的一 个可选参数,默认 ...
- 【GoLang】GoLang map 非线程安全 & 并发度写优化
Catena (时序存储引擎)中有一个函数的实现备受争议,它从 map 中根据指定的 name 获取一个 metricSource.每一次插入操作都会至少调用一次这个函数,现实场景中该函数调用更是频繁 ...
- Java 面试宝典!并发编程 71 道题及答案全送上!
金九银十跳槽季已经开始,作为 Java 开发者你开始刷面试题了吗?别急,我整理了71道并发相关的面试题,看这一文就够了! 1.在java中守护线程和本地线程区别? java中的线程分为两种:守护线程( ...
- 虚拟线程 - VirtualThread源码透视
前提 JDK19于2022-09-20发布GA版本,该版本提供了虚拟线程的预览功能.下载JDK19之后翻看了一下有关虚拟线程的一些源码,跟早些时候的Loom项目构建版本基本并没有很大出入,也跟第三方J ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- java线程高并发编程
java线程具体解释及高并发编程庖丁解牛 线程概述: 祖宗: 说起java高并发编程,就不得不提起一位老先生Doug Lea,这位老先生可不得了.看看百度百科对他的评价,一点也不为过: 假设IT的历史 ...
- 《java学习三》并发编程 -------线程池原理剖析
阻塞队列与非阻塞队 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到 ...
- Java并发编程系列-(2) 线程的并发工具类
2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...
- Java多线程并发03——在Java中线程是如何调度的
在前两篇文章中,我们已经了解了关于线程的创建与常用方法等相关知识.接下来就来了解下,当你运行线程时,线程是如何调度的.关注我的公众号「Java面典」了解更多 Java 相关知识点. 多任务系统往往需要 ...
随机推荐
- Excel百万数据高性能导出方案!
前言 在我们的日常工作中,经常会有Excel数据导出的需求. 但可能会遇到性能和内存的问题. 今天这篇文章跟大家一起聊聊Excel高性能导出的方案,希望对你会有所帮助. 1 传统方案的问题 很多小伙伴 ...
- 信息资源管理综合题之“X大师设计瓷砖给公司生产并检验和销售”
一.X大师是一位德高望重的设计大师,现在为A公司设计了一套陶瓷地砖,准备交由B公司进行批量生产.B企业按照GB/T 3810.13-1999陶瓷砖试验方法对该产品进行检测,检测合格,并推向市场 1.按 ...
- MySql的information_schema.processlist库学习之"如何检测出大数据sql查询"
1.如何通过MySql检测出大数据sql查询 一般数据库都会存在:information_schema数据库 检测出大数据sql查询[time时间越长说明,数据量越大,要根据公司的限度来衡量,我的思路 ...
- 遇到过的错误之“日期计算错误,Java8API导致Unsupported unit: Seconds【时间类错误】"
一.问题 场景:在计算相差天数时爆出的错误 报错内容:java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Se ...
- 联邦学习图像分类实战:基于FATE与PyTorch的隐私保护机器学习系统构建指南
引言 在数据孤岛与隐私保护需求并存的今天,联邦学习(Federated Learning)作为分布式机器学习范式,为医疗影像分析.金融风控.智能交通等领域提供了创新解决方案.本文将基于FATE框架与P ...
- Kubernetes 调度器打分算法详解:LeastAllocated 与 NodeAffinity
1️⃣ NodeResourcesLeastAllocated(资源最少分配) 目标 优先将 Pod 调度到资源使用率最低的节点,防止热点节点,尽量实现负载均衡. 打分算法原理 对每个节点计算 CPU ...
- 【2020.11.25提高组模拟】树的解构(deconstruct) 题解
[2020.11.25提高组模拟]树的解构(deconstruct) 题解 题目描述 给一棵以\(1\)为根的外向树,进行\((n-1)\)次删边操作,每次都会从没有删掉的边中等概率地删掉一条边\(a ...
- 有限Abel群的结构(2)
版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖.如要转贴,必须注明原文网址 https://www.cnblogs.com/Colin-Cai/p/18791060.html 作者:窗户 ...
- 替换GitLab的方案之Gitea
概述 官网:https://docs.gitea.com/zh-cn/ GitHub地址:https://github.com/go-gitea/gitea Gitea 是一个轻量级的 DevOps ...
- 20250626 - SiloFinance 攻击事件: 恶意输入参数导致借款授权额度盗用
背景信息 SiloFinance 是一个借贷协议,用户可以进行抵押借贷,也可以授权其他用户使用其借贷额度.本次攻击的原因是 LeverageUsingSiloFlashloanWithGeneralS ...