[{"data":1,"prerenderedAt":603},["ShallowReactive",2],{"content:article:java-virtual-threads-and-backpressure":3,"content:article-surround:\u002Farticles\u002Fjava-virtual-threads-and-backpressure":594},{"id":4,"title":5,"body":6,"category":577,"cover":578,"date":579,"description":580,"draft":581,"extension":582,"featured":581,"meta":583,"navigation":124,"path":584,"seo":585,"stem":586,"tags":587,"updated":592,"__hash__":593},"articles\u002Farticles\u002Fjava-virtual-threads-and-backpressure.md","Java 虚拟线程为什么不等于无限并发：从等待型任务说起",{"type":7,"value":8,"toc":569},"minimark",[9,13,20,25,28,31,34,41,44,61,421,424,455,462,472,475,478,481,484,487,490,496,500,510,513,516,519,562,565],[10,11,12],"p",{},"一个接口要请求外部模型、查询数据库、读取远程文件，大部分时间可能都在等。为了同时处理更多请求，传统做法往往是调大线程池；但平台线程不是没有成本，线程数量也不能无限增加。",[10,14,15,16],{},"虚拟线程为这种“每个任务按顺序写代码、执行中经常阻塞等待”的场景提供了另一种选择。它在 JDK 21 成为正式特性，目标重点是提高大量等待型任务的并发承载能力，而不是让单个计算任务自动变快。",[17,18,19],"span",{},"1",[21,22,24],"h2",{"id":23},"线程轻了真正的资源没有变多","线程轻了，真正的资源没有变多",[10,26,27],{},"可以把虚拟线程理解为由 JVM 调度的轻量线程。适用的阻塞操作发生时，运行它的平台线程可以释放出来执行其他虚拟线程。具体行为还取决于操作、库实现和 JDK 版本，不能把“阻塞都没有成本”当成保证。",[10,29,30],{},"假设数据库连接池只有 20 个连接，即使创建 10,000 个虚拟线程，数据库也不会自动变成能同时处理 10,000 个查询。等待只是从平台线程池队列转移到了获取连接的地方。如果每个任务还持有大对象，内存仍然可能耗尽。",[10,32,33],{},"因此需要分清三件事：线程用于承载执行；并发限制用于保护有限资源；入口限流或有界队列用于控制系统接收多少工作。只改变第一件事，后两件事仍要设计。",[10,35,36],{},[37,38],"img",{"alt":39,"src":40},"虚拟线程与下游资源限制","\u002Fimages\u002Fengineering-notes\u002F07-virtual-threads.png",[21,42,43],{"id":43},"一个带并发上限的完整示例",[10,45,46,47,51,52,56,57,60],{},"下面的程序面向 ",[48,49,50],"strong",{},"Java 21 及以上","，使用 ",[53,54,55],"code",{},"Thread.sleep"," 模拟等待，不访问外部服务。它演示同一时间最多八个任务进入受保护区域，不是性能基准。将代码保存为 ",[53,58,59],{},"VirtualThreadBulkhead.java"," 即可按后面的命令运行。",[62,63,68],"pre",{"className":64,"code":65,"language":66,"meta":67,"style":67},"language-java shiki shiki-themes github-light github-dark","import java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.Semaphore;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class VirtualThreadBulkhead {\n    private static final Semaphore GATE = new Semaphore(8);\n    private static final AtomicInteger ACTIVE = new AtomicInteger();\n    private static final AtomicInteger PEAK = new AtomicInteger();\n\n    private static String callDownstream(int id) throws InterruptedException {\n        \u002F\u002F 只在成功拿到许可后进入 finally，避免误释放许可。\n        if (!GATE.tryAcquire(2, TimeUnit.SECONDS)) {\n            return \"REJECTED:\" + id;\n        }\n\n        try {\n            int active = ACTIVE.incrementAndGet();\n            PEAK.accumulateAndGet(active, Math::max);\n            Thread.sleep(40); \u002F\u002F 模拟可中断的等待，不代表真实接口耗时。\n            return \"OK:\" + id;\n        } finally {\n            ACTIVE.decrementAndGet();\n            GATE.release();\n        }\n    }\n\n    public static void main(String[] args) throws Exception {\n        List\u003CFuture\u003CString>> futures = new ArrayList\u003C>();\n        int succeeded = 0;\n        int rejected = 0;\n\n        try (ExecutorService executor =\n                     Executors.newVirtualThreadPerTaskExecutor()) {\n            for (int i = 0; i \u003C 200; i++) {\n                final int id = i;\n                futures.add(executor.submit(() -> callDownstream(id)));\n            }\n\n            for (Future\u003CString> future : futures) {\n                String result = future.get();\n                if (result.startsWith(\"OK:\")) {\n                    succeeded++;\n                } else {\n                    rejected++;\n                }\n            }\n        }\n\n        if (PEAK.get() > 8) {\n            throw new IllegalStateException(\"并发上限被突破\");\n        }\n        System.out.printf(\"ok=%d rejected=%d peak=%d%n\",\n                succeeded, rejected, PEAK.get());\n    }\n}\n","java","",[53,69,70,77,83,89,95,101,107,113,119,126,132,138,144,150,155,161,167,173,179,185,190,196,202,208,214,220,226,232,238,243,249,254,260,266,272,278,283,289,295,301,307,313,319,324,330,336,342,348,354,360,366,371,376,381,387,393,398,404,410,415],{"__ignoreMap":67},[17,71,74],{"class":72,"line":73},"line",1,[17,75,76],{},"import java.util.ArrayList;\n",[17,78,80],{"class":72,"line":79},2,[17,81,82],{},"import java.util.List;\n",[17,84,86],{"class":72,"line":85},3,[17,87,88],{},"import java.util.concurrent.ExecutorService;\n",[17,90,92],{"class":72,"line":91},4,[17,93,94],{},"import java.util.concurrent.Executors;\n",[17,96,98],{"class":72,"line":97},5,[17,99,100],{},"import java.util.concurrent.Future;\n",[17,102,104],{"class":72,"line":103},6,[17,105,106],{},"import java.util.concurrent.Semaphore;\n",[17,108,110],{"class":72,"line":109},7,[17,111,112],{},"import java.util.concurrent.TimeUnit;\n",[17,114,116],{"class":72,"line":115},8,[17,117,118],{},"import java.util.concurrent.atomic.AtomicInteger;\n",[17,120,122],{"class":72,"line":121},9,[17,123,125],{"emptyLinePlaceholder":124},true,"\n",[17,127,129],{"class":72,"line":128},10,[17,130,131],{},"public class VirtualThreadBulkhead {\n",[17,133,135],{"class":72,"line":134},11,[17,136,137],{},"    private static final Semaphore GATE = new Semaphore(8);\n",[17,139,141],{"class":72,"line":140},12,[17,142,143],{},"    private static final AtomicInteger ACTIVE = new AtomicInteger();\n",[17,145,147],{"class":72,"line":146},13,[17,148,149],{},"    private static final AtomicInteger PEAK = new AtomicInteger();\n",[17,151,153],{"class":72,"line":152},14,[17,154,125],{"emptyLinePlaceholder":124},[17,156,158],{"class":72,"line":157},15,[17,159,160],{},"    private static String callDownstream(int id) throws InterruptedException {\n",[17,162,164],{"class":72,"line":163},16,[17,165,166],{},"        \u002F\u002F 只在成功拿到许可后进入 finally，避免误释放许可。\n",[17,168,170],{"class":72,"line":169},17,[17,171,172],{},"        if (!GATE.tryAcquire(2, TimeUnit.SECONDS)) {\n",[17,174,176],{"class":72,"line":175},18,[17,177,178],{},"            return \"REJECTED:\" + id;\n",[17,180,182],{"class":72,"line":181},19,[17,183,184],{},"        }\n",[17,186,188],{"class":72,"line":187},20,[17,189,125],{"emptyLinePlaceholder":124},[17,191,193],{"class":72,"line":192},21,[17,194,195],{},"        try {\n",[17,197,199],{"class":72,"line":198},22,[17,200,201],{},"            int active = ACTIVE.incrementAndGet();\n",[17,203,205],{"class":72,"line":204},23,[17,206,207],{},"            PEAK.accumulateAndGet(active, Math::max);\n",[17,209,211],{"class":72,"line":210},24,[17,212,213],{},"            Thread.sleep(40); \u002F\u002F 模拟可中断的等待，不代表真实接口耗时。\n",[17,215,217],{"class":72,"line":216},25,[17,218,219],{},"            return \"OK:\" + id;\n",[17,221,223],{"class":72,"line":222},26,[17,224,225],{},"        } finally {\n",[17,227,229],{"class":72,"line":228},27,[17,230,231],{},"            ACTIVE.decrementAndGet();\n",[17,233,235],{"class":72,"line":234},28,[17,236,237],{},"            GATE.release();\n",[17,239,241],{"class":72,"line":240},29,[17,242,184],{},[17,244,246],{"class":72,"line":245},30,[17,247,248],{},"    }\n",[17,250,252],{"class":72,"line":251},31,[17,253,125],{"emptyLinePlaceholder":124},[17,255,257],{"class":72,"line":256},32,[17,258,259],{},"    public static void main(String[] args) throws Exception {\n",[17,261,263],{"class":72,"line":262},33,[17,264,265],{},"        List\u003CFuture\u003CString>> futures = new ArrayList\u003C>();\n",[17,267,269],{"class":72,"line":268},34,[17,270,271],{},"        int succeeded = 0;\n",[17,273,275],{"class":72,"line":274},35,[17,276,277],{},"        int rejected = 0;\n",[17,279,281],{"class":72,"line":280},36,[17,282,125],{"emptyLinePlaceholder":124},[17,284,286],{"class":72,"line":285},37,[17,287,288],{},"        try (ExecutorService executor =\n",[17,290,292],{"class":72,"line":291},38,[17,293,294],{},"                     Executors.newVirtualThreadPerTaskExecutor()) {\n",[17,296,298],{"class":72,"line":297},39,[17,299,300],{},"            for (int i = 0; i \u003C 200; i++) {\n",[17,302,304],{"class":72,"line":303},40,[17,305,306],{},"                final int id = i;\n",[17,308,310],{"class":72,"line":309},41,[17,311,312],{},"                futures.add(executor.submit(() -> callDownstream(id)));\n",[17,314,316],{"class":72,"line":315},42,[17,317,318],{},"            }\n",[17,320,322],{"class":72,"line":321},43,[17,323,125],{"emptyLinePlaceholder":124},[17,325,327],{"class":72,"line":326},44,[17,328,329],{},"            for (Future\u003CString> future : futures) {\n",[17,331,333],{"class":72,"line":332},45,[17,334,335],{},"                String result = future.get();\n",[17,337,339],{"class":72,"line":338},46,[17,340,341],{},"                if (result.startsWith(\"OK:\")) {\n",[17,343,345],{"class":72,"line":344},47,[17,346,347],{},"                    succeeded++;\n",[17,349,351],{"class":72,"line":350},48,[17,352,353],{},"                } else {\n",[17,355,357],{"class":72,"line":356},49,[17,358,359],{},"                    rejected++;\n",[17,361,363],{"class":72,"line":362},50,[17,364,365],{},"                }\n",[17,367,369],{"class":72,"line":368},51,[17,370,318],{},[17,372,374],{"class":72,"line":373},52,[17,375,184],{},[17,377,379],{"class":72,"line":378},53,[17,380,125],{"emptyLinePlaceholder":124},[17,382,384],{"class":72,"line":383},54,[17,385,386],{},"        if (PEAK.get() > 8) {\n",[17,388,390],{"class":72,"line":389},55,[17,391,392],{},"            throw new IllegalStateException(\"并发上限被突破\");\n",[17,394,396],{"class":72,"line":395},56,[17,397,184],{},[17,399,401],{"class":72,"line":400},57,[17,402,403],{},"        System.out.printf(\"ok=%d rejected=%d peak=%d%n\",\n",[17,405,407],{"class":72,"line":406},58,[17,408,409],{},"                succeeded, rejected, PEAK.get());\n",[17,411,413],{"class":72,"line":412},59,[17,414,248],{},[17,416,418],{"class":72,"line":417},60,[17,419,420],{},"}\n",[10,422,423],{},"运行方式：",[62,425,429],{"className":426,"code":427,"language":428,"meta":67,"style":67},"language-bash shiki shiki-themes github-light github-dark","javac --release 21 VirtualThreadBulkhead.java\njava VirtualThreadBulkhead\n","bash",[53,430,431,448],{"__ignoreMap":67},[17,432,433,437,441,444],{"class":72,"line":73},[17,434,436],{"class":435},"sScJk","javac",[17,438,440],{"class":439},"sj4cs"," --release",[17,442,443],{"class":439}," 21",[17,445,447],{"class":446},"sZZnC"," VirtualThreadBulkhead.java\n",[17,449,450,452],{"class":72,"line":79},[17,451,66],{"class":435},[17,453,454],{"class":446}," VirtualThreadBulkhead\n",[10,456,457,458,461],{},"这里的 200 是有限的演示输入。生产系统不应先把任意多请求全部提交，再认为一个 ",[53,459,460],{},"Semaphore"," 已经解决过载。入口仍应设置接收上限、排队时限或拒绝策略。",[10,463,464,467,468,471],{},[53,465,466],{},"tryAcquire"," 只限制等待许可的时间，不限制真正的远程请求时间。替换成 HTTP 或数据库调用后，还要配置调用超时、连接池获取超时，并按业务需要管理整体截止时间。",[53,469,470],{},"Future.get()"," 超时也不等于远程动作一定停止；取消通常只是请求中断，底层客户端是否响应必须验证。",[10,473,474],{},"以上 Java 21 示例经过代码审阅，但未附编译运行验证；这里不提供运行结果或吞吐量数字。它适合用来观察任务与许可的关系，真实服务还需要在对应 JDK 和依赖环境中验证。",[21,476,477],{"id":477},"并发上限和每秒请求数不是一回事",[10,479,480],{},"八个许可意味着最多八个调用同时进行。若接口响应很快，一秒内仍可能发出很多次调用。因此，模型服务的“每分钟请求数”限制，需要速率限制器或请求预算；连接数和并发数限制，则由信号量、连接池等机制处理。",[10,482,483],{},"如果不同下游容量不同，最好分别控制。给数据库查询、模型推理、文件上传共用一个许可池，可能让慢上传阻塞本来可以快速完成的查询。反过来，每个请求自己创建一个新的信号量，也无法形成应用级总限制。",[21,485,486],{"id":486},"哪些任务不适合指望虚拟线程提速",[10,488,489],{},"CPU 密集型计算仍然受处理器资源限制。给同一段图像计算开更多虚拟线程，不会增加物理计算能力，还可能增加调度成本。此时更应考虑算法、数据布局和合理的计算并行度。",[10,491,492,493],{},"还要注意每任务资源。平台线程池中某个 ThreadLocal 缓存原本只创建几十份，改成每任务一个虚拟线程后，可能出现更多实例。适合共享且线程安全的资源应以受控方式共享，不能机械照搬“每线程一个大对象”。",[17,494,495],{},"2",[21,497,499],{"id":498},"关于-synchronized要写清-jdk-版本","关于 synchronized，要写清 JDK 版本",[10,501,502,503,506,507],{},"在 JDK 21 中，虚拟线程在某些 ",[53,504,505],{},"synchronized"," 相关场景阻塞时可能固定住承载线程，即 pinning。JDK 24 的相关改进消除了由 Java monitor 导致的一类固定问题，但这不表示所有 native\u002Fforeign 等场景都不再有类似限制。[2]",[17,508,509],{},"3",[10,511,512],{},"因此，不能在所有版本上笼统写“虚拟线程绝对不能用 synchronized”，也不能说升级之后无需再分析阻塞。应结合实际 JDK、依赖库、线程转储或 JFR 等工具查明瓶颈。",[10,514,515],{},"虚拟线程最有价值的地方，是保留直观的顺序代码，同时承载更多等待中的任务。上线前真正需要验证的，是下游容量、排队时间、内存和取消行为，而不是只数创建了多少线程。",[21,517,518],{"id":518},"参考资料",[520,521,522,536,546],"ul",{},[523,524,525,527,528,535],"li",{},[17,526,19],{}," ",[529,530,534],"a",{"href":531,"rel":532},"https:\u002F\u002Fopenjdk.org\u002Fjeps\u002F444",[533],"nofollow","OpenJDK：JEP 444"," — 虚拟线程的目标与正式引入。",[523,537,538,527,540,545],{},[17,539,495],{},[529,541,544],{"href":542,"rel":543},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F21\u002Fcore\u002Fvirtual-threads.html",[533],"Oracle：Java 21 Virtual Threads"," — 使用方式与限制。",[523,547,548,527,550,555,556,561],{},[17,549,509],{},[529,551,554],{"href":552,"rel":553},"https:\u002F\u002Fdocs.oracle.com\u002Fen\u002Fjava\u002Fjavase\u002F24\u002Fcore\u002Fvirtual-threads.html",[533],"Oracle：Java 24 Virtual Threads"," — 新版本中的行为说明；另见 ",[529,557,560],{"href":558,"rel":559},"https:\u002F\u002Fopenjdk.org\u002Fjeps\u002F491",[533],"JEP 491","。",[10,563,564],{},"本文不报告性能实测。资料核对日期：2026-10-08。",[566,567,568],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":67,"searchDepth":85,"depth":85,"links":570},[571,572,573,574,575,576],{"id":23,"depth":79,"text":24},{"id":43,"depth":79,"text":43},{"id":477,"depth":79,"text":477},{"id":486,"depth":79,"text":486},{"id":498,"depth":79,"text":499},{"id":518,"depth":79,"text":518},"后端架构","\u002Fimages\u002Fcovers\u002Fbackend-series.jpg","2025-11-18","用完整的 Java 21 示例理解虚拟线程、Semaphore 和下游保护，并区分 JDK 21 与 JDK 24 的 pinning 行为。",false,"md",{},"\u002Farticles\u002Fjava-virtual-threads-and-backpressure",{"title":5,"description":580},"articles\u002Fjava-virtual-threads-and-backpressure",[588,589,590,591],"Java","并发","虚拟线程","后端",null,"8CRm_hqF-uNkcHeS_CGY_9K7TEyy_SiTgZrLbDvg7UA",[595,599],{"title":596,"path":597,"date":598},"Agent 的记忆，不止 ChatMemory：从五轮对话到带冲突检测的记忆系统","\u002Farticles\u002Fagent-memory-pipeline","2025-11-20",{"title":600,"path":601,"date":602},"别让 Agent 直接动手：我用\"草稿-确认\"双阶段管住高风险操作","\u002Farticles\u002Fagent-draft-confirm-safety","2025-10-08",1792063306798]