把newFixedThreadPool(8)换成newVirtualThreadPerTaskExecutor()时原来的并发边界也可能一起变了。如果任务同步调用下游并等待返回固定池的 8 个工作线程原本还限制着同时进行的调用数。同样提交 200 个虚拟线程任务本例无并发限制时调用区峰值达到 200先取得共享信号量的 8 个许可峰值就保持为 8。Java 21 的虚拟线程执行器按任务创建线程数量没有固定的池大小上限。Executors 的方法说明明确给出了这个约定。运行环境是 Windows、OpenJDK 21.0.213-58。程序只依赖 JDK用受控等待让进入调用区的任务停留在那里便于观察并发数。先把同时执行的数量留在现场任务若只执行一行println可能在其他任务开始前就结束。这里增加一道等待门任务进入“下游调用区”后先停下来主线程取完快照再统一放行。两组各提交 200 个任务并使用同一个虚拟线程执行器工厂。第一组直接进入调用区第二组先申请 8 个共享许可中的一个。计数器只记录已经进入、尚未退出调用区的任务等待许可的任务不算进去。调用区由内存中的等待夹具代替没有实际发出远程请求所以这里观察到的是并发边界。完整代码保存为src/VirtualThreadLimits.javaimportjava.util.ArrayList;importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.Executors;importjava.util.concurrent.Future;importjava.util.concurrent.Semaphore;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.atomic.AtomicInteger;publicclassVirtualThreadLimits{privatestaticfinalintTASKS200;privatestaticfinalintLIMIT8;privatestaticvoidawait(CountDownLatchlatch)throwsInterruptedException{if(!latch.await(10,TimeUnit.SECONDS)){thrownewIllegalStateException(fixture timed out);}}privatestaticvoidrunCase(booleanlimited)throwsException{Semaphorepermitslimited?newSemaphore(LIMIT,true):null;intexpectedFirstWavelimited?LIMIT:TASKS;varreadynewCountDownLatch(TASKS);varstartnewCountDownLatch(1);varfirstWavenewCountDownLatch(expectedFirstWave);varreleasenewCountDownLatch(1);varactivenewAtomicInteger();varpeaknewAtomicInteger();varcompletednewAtomicInteger();varvirtualTasksnewAtomicInteger();varfuturesnewArrayListFuture?();intheldActive;try(varexecutorExecutors.newVirtualThreadPerTaskExecutor()){try{for(inti0;iTASKS;i){futures.add(executor.submit(()-{ready.countDown();await(start);if(Thread.currentThread().isVirtual()){virtualTasks.incrementAndGet();}if(permits!null){permits.acquire();}try{intnowactive.incrementAndGet();peak.accumulateAndGet(now,Math::max);firstWave.countDown();try{await(release);// A held-open downstream operation.}finally{active.decrementAndGet();}completed.incrementAndGet();}finally{if(permits!null){permits.release();}}returnnull;}));}await(ready);start.countDown();await(firstWave);heldActiveactive.get();}finally{start.countDown();release.countDown();}for(varfuture:futures){future.get(10,TimeUnit.SECONDS);}}if(heldActive!expectedFirstWave||peak.get()!expectedFirstWave||completed.get()!TASKS||virtualTasks.get()!TASKS||active.get()!0||(permits!nullpermits.availablePermits()!LIMIT)){thrownewAssertionError(unexpected concurrency result);}System.out.printf({\case\:\%s\,\tasks\:%d,\virtualTasks\:%d,\heldActive\:%d,\peakActive\:%d,\completed\:%d,\finalActive\:%d,\finalPermits\:%d}%n,limited?semaphore-8:unlimited,TASKS,virtualTasks.get(),heldActive,peak.get(),completed.get(),active.get(),permitsnull?-1:permits.availablePermits());}publicstaticvoidmain(String[]args)throwsException{System.out.println(java.runtime.versionSystem.getProperty(java.runtime.version));runCase(false);runCase(true);System.out.println(PASS);}}在文章目录执行javac--release 21-encoding UTF-8-d out src/VirtualThreadLimits.java java-cpout VirtualThreadLimits--release 21指定编译目标。若找不到newVirtualThreadPerTaskExecutor先核对java -version和javac -version确保两者都支持本文目标版本。正常运行最后输出PASS夹具等待超过 10 秒会报错。两组都是 200 个虚拟线程峰值分别为 200 和 8本次以三个独立 Java 进程重复执行两组结果每次一致进程无并发限制的峰值共享 8 个许可的峰值每组完成任务数120082002200820032008200第一组的原始结果行是{case:unlimited,tasks:200,virtualTasks:200,heldActive:200,peakActive:200,completed:200,finalActive:0,finalPermits:-1}第二组为{case:semaphore-8,tasks:200,virtualTasks:200,heldActive:8,peakActive:8,completed:200,finalActive:0,finalPermits:8}virtualTasks200说明两组确实都在虚拟线程里执行。peakActive才是本文关心的同时调用数finalActive0、许可最终回到 8说明任务完成后没有把调用区计数或许可留住。第一组的-1只是“未启用信号量”的输出标记。第一波任务被主动留在调用区200 和 8 因而是受控夹具下的结果。它说明共享信号量确实限制了这一段代码的并发真实服务的自然峰值、吞吐和耗时还需要另测。许可需要围住哪一段代码先acquire()成功后才进入包含release()的try/finally。这样等待许可时若被中断就不会释放一个自己没有取得的许可。Semaphore 的 API 约定也允许等待中的acquire()响应中断。放到应用里许可通常围住真正消耗有限资源的那段调用。若把解析请求、读缓存、拼响应都围进去和下游无关的工作也会占用名额。若拿到许可后立即释放再去发请求限制则根本没有覆盖远程调用。还有一个常见的无效写法每个请求内部都new Semaphore(8)。这些对象各有自己的 8 个许可彼此没有竞争关系。本文把信号量放在这一组任务共享的位置真实应用也需要先明确它代表哪一种资源以及哪些调用路径必须共享它。加了信号量仍然要处理等待假设远程接口允许同时 8 个调用信号量可以表达这个限制但它不会自动决定第 9 个请求该等多久也不会限制每秒可以发出多少次请求。并发数、请求速率、入口积压是三个不同问题。阻塞式acquire()方便观察这次实验实际请求还需要等待预算。请求有截止时间时应把等许可、建连接和读取响应放进同一份预算等许可太久可以通过带超时的tryAcquire或上层准入规则结束。等待的虚拟线程还持有请求正文、上下文等资源这部分成本也要计算。也不必给所有调用再套一层信号量。例如数据库连接池已经把可用连接数限制在固定范围再加同样大小的一层未必带来收益。要先看实际瓶颈和其他调用入口避免双重排队。Oracle 的虚拟线程采用指南讨论了用信号量限制资源访问并指出连接池自身也承担类似作用。本文选 8 是为了让两个实验分支的差别容易观察不是给服务推荐一个通用并发值。真实值要结合下游约定、连接容量、调用时长和允许等待的请求数来确定进程里放 8 个许可也不会自动变成多个部署副本合计 8 个。迁移前先列出原线程池大小隐含的资源限制再逐项确定由连接池、共享信号量还是入口准入来承接。这个名单比直接沿用原来的数字更有用。
企业数字化 ERP 产品动态
相关推荐
Bot 时代来了?从能力封装到数字员工,AI Agent 正在发生什么变化 最近,AI Agent 领域出现了一个值得关注的变化:越来越多的讨论开始从 Skills 转向 Bot。
过去,我们关注如何给大模型增加能力:设计 Prompt、封装 Skills、接入 MCP、编排 Workflow。
现在,新的问题开始出现:… · 2026/11/3 19:33:24
C++一个函数如何调用其他.cpp文件中的函数 一个函数调用其他.cpp文件中的函数使用VC或VS创建C项目的时候,会自动产生许多文件夹,其中有一个文件夹->源文件:在该文件下可以自定义许多.cpp文件,但是需要注意的是这里面的各个文件只能有一个文件中含有main()函数࿰… · 2026/11/3 19:33:24
空间智能终端正在从单点设备走向系统协同,行业竞争逻辑已经变了 过去几年,空间智能终端的竞争集中在硬件本身:摄像头像素、门锁识别速度、面板响应时间、传感器精度。但当一个项目从几百台设备扩展到几万台,从单一住宅延伸到公寓、酒店、商业、园区多业态共存时,真正的瓶颈不再是某个终端够不够… · 2026/11/3 19:32:24
支付系统核心架构设计与实践指南 1. 支付核心系统设计概述 支付系统作为现代商业基础设施的核心组件,其设计质量直接影响着企业的资金流转效率和用户体验。一套优秀的支付核心系统应当具备高可用性、强一致性、可扩展性和安全性等关键特性。在实际项目中,我们通常会面临交易峰值波动、资… · 2026/11/3 20:13:46
医疗大数据预测分析:核心技术架构与应用实践 1. 医疗保健领域的大数据预测分析概述医疗行业正经历着从经验医学向精准医学的转型,而大数据预测分析正是这一变革的核心驱动力。我在三甲医院信息化建设项目中亲历了这种转变——过去医生主要依靠个人经验和有限的临床数据做决策,现在则能够整合患者全生… · 2026/11/3 20:13:46
支付核心系统架构设计与关键技术实现 1. 支付核心系统设计概述支付系统作为现代商业的基础设施,其重要性不言而喻。一套设计良好的支付核心系统能够支撑日均千万级交易,保证资金安全流转,同时为业务创新提供坚实基础。我在金融科技领域深耕多年,参与过多个大型支付系统… · 2026/11/3 20:13:46
GPU加速大数据可视化:技术原理与性能优化实践 1. 大数据可视化与GPU渲染的黄金组合大数据可视化正面临着一个关键瓶颈:当数据量突破千万级甚至亿级时,传统CPU渲染方式开始力不从心。我曾参与一个智慧城市交通流量分析项目,当尝试在网页端渲染全市出租车GPS热力图时,Chrome浏览… · 2026/11/3 20:13:46
编程基础:分支与循环语句的核心原理与应用 1. 分支与循环语句基础解析在编程世界中,分支与循环语句就像交通信号灯和环形公路,控制着程序执行的流向和重复。作为程序逻辑的骨架结构,它们几乎出现在所有编程语言中,构成了算法实现的基础工具集。分支语句(条件语句… · 2026/11/3 20:13:46
Tableau Server彻底卸载指南与残留清理技巧 1. 问题背景:为什么Tableau Server卸载会不干净?Tableau Server作为企业级商业智能平台,其安装过程会深度集成到系统环境中。不同于普通桌面软件,它的卸载残留问题主要源于三个层面:服务组件复杂性:默认安装… · 2026/11/3 20:12:46
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46