Java 虚拟线程原理与性能压测实测
一、虚拟线程要解决什么问题Java 传统线程平台线程是对操作系统线程的 1:1 封装——每个 Java 线程背后是一个 OS 线程。OS 线程很重一个线程栈默认占 1MB 内存线程切换要陷入内核态开销约 1~3μs。这导致两个硬限制限制维度平台线程虚拟线程数量上限~数千受内存限制200线程≈200MB栈~百万每个约几KB创建开销~1ms系统调用~1μs用户态切换开销1~3μs内核态切换~100ns用户态切换栈内存固定 1MB动态增长按需分配阻塞代价整个 OS 线程阻塞仅虚拟线程挂起载体线程释放虚拟线程的核心思想把线程的调度从 OS 内核搬到 JVM 用户态。虚拟线程运行在少量载体线程ForkJoinPool上遇到阻塞操作时自动让出载体线程让其他虚拟线程继续执行。这样一万个虚拟线程可以跑在几十个载体线程上内存和切换开销都大幅降低。二、核心原理调度模型与内存结构2.1 虚拟线程的调度模型虚拟线程由 JVM 内部的ForkJoinPool调度载体线程数默认等于 CPU 核心数可通过jdk.virtualThreadScheduler.parallelism调整虚拟线程调度模型JDK 21 ​ 载体线程池ForkJoinPool默认 CPU核心数 ┌──────────────────────────────────────────┐ │ Carrier-1 Carrier-2 Carrier-3 Carrier-4 │ │ (运行VT-A) (运行VT-C) (运行VT-E) (运行VT-G) │ └──────────────────────────────────────────┘ ↑ 阻塞时让出 ↑ 唤醒后重新挂载 │ │ ┌─────┴──────────────────────┴─────────────┐ │ 虚拟线程队列百万级 │ │ VT-A(running) VT-B(parked) VT-C(running) │ │ VT-D(parked) VT-E(running) VT-F(parked) │ │ VT-G(running) VT-H(parked) 共百万级 │ └────────────────────────────────────────────┘关键机制当虚拟线程执行阻塞操作如socket.read()、Thread.sleep()时JVM 会自动把它从载体线程上卸载unmount载体线程转去执行下一个就绪的虚拟线程。阻塞操作完成后虚拟线程被重新调度到某个载体线程上继续执行remount。// JDK 21创建和运行虚拟线程的几种方式 public class VirtualThreadDemo { ​ // 方式一直接创建虚拟线程并启动 public void startDirect() { Thread vt Thread.ofVirtual().start(() - { System.out.println(虚拟线程运行中 Thread.currentThread()); }); } ​ // 方式二通过虚拟线程 Per-Task Executor 提交任务 public void startViaExecutor() throws Exception { // 每个任务一个虚拟线程Executors.newVirtualThreadPerTaskExecutor try (var executor Executors.newVirtualThreadPerTaskExecutor()) { // 提交 10000 个任务每个跑在一个虚拟线程上 var futures new java.util.ArrayListjava.util.concurrent.FutureInteger(); for (int i 0; i 10000; i) { final int taskId i; futures.add(executor.submit(() - { Thread.sleep(100); // 模拟 IO 阻塞 return taskId; })); } // 等待全部完成 int sum 0; for (var f : futures) sum f.get(); System.out.println(完成 10000 个任务sum sum); } } ​ // 方式三判断当前是否为虚拟线程 public void checkThreadType() { boolean isVirtual Thread.currentThread().isVirtual(); System.out.println(当前线程是虚拟线程吗 isVirtual); } } 原理要点虚拟线程的Thread对象和平台线程一样有完整的线程 APIgetName、getStackTrace等但底层不绑定 OS 线程。isVirtual()是 JDK 21 新增的判断方法。2.2 栈结构为什么虚拟线程内存这么小平台线程的栈在创建时就分配固定大小默认 1MB无论用没用到都占着。虚拟线程的栈是动态的——初始只有几百字节的栈帧在堆上随着方法调用深度增长按需扩展方法返回后收缩。栈维度平台线程虚拟线程存储位置OS 线程栈连续内存Java 堆对象形式初始大小1MB固定~数百字节最大深度受栈大小限制受堆内存限制增长方式不可增长按需扩展copy 栈帧阻塞时栈内存仍占用栈帧快照存堆上载体线程释放⚠️ 注意虚拟线程的栈存在堆上意味着大量虚拟线程会增加 GC 压力。但每个虚拟线程的栈通常只有几 KB远小于平台线程的 1MB百万虚拟线程也只占几 GB 堆。三、载体线程与Continuation阻塞挂起的底层虚拟线程能阻塞不占线程的秘密在于Continuation——JDK 内部对执行栈的快照与恢复机制。// JDK 21 内部机制简化展示原理Continuation 是内部 API 不直接暴露 // 当虚拟线程执行到阻塞操作如 Socket.read时 ​ // 1. JVM 检测到阻塞操作通过 Unsafe.park 或 jdk.internal.vm.Continuation // 2. 将当前虚拟线程的执行栈快照保存为 Continuation 对象存到堆上 // 3. 从载体线程卸载unmount载体线程回到 ForkJoinPool 执行下一个任务 // 4. 阻塞操作完成后如数据到达Continuation 被提交回调度器 // 5. 调度器将 Continuation 挂载到某个载体线程remount恢复执行 ​ // 这个过程对开发者透明代码写法和平台线程完全一样 Thread vt Thread.ofVirtual().start(() - { // 下面这行看似阻塞了线程实际只挂起了虚拟线程载体线程是自由的 var data socket.read(buffer); // 阻塞 IO自动 unmount/remount process(data); });关键JDK 21 对java.util.concurrent的阻塞操作LockSupport.park、BlockingQueue.take、Thread.sleep、Socket I/O都做了适配能自动触发虚拟线程的 unmount/remount。这就是虚拟线程用同步代码风格获得异步性能的核心。四、实测环境准备项目配置操作系统Windows 11 22H2CPUAMD Ryzen 7 5800H 8核 3.2GHz内存32GB DDR4 3200MHzJDKOpenJDK 21.0.2 (Temurin)JVM 堆-Xms4g -Xmx4g压测工具JMHJava Microbenchmark Harness压测代码模拟 IO 密集型 Web 服务的场景package com.example.vtbench; ​ import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import java.util.concurrent.*; import java.util.ArrayList; import java.util.List; ​ // 对比平台线程池 vs 虚拟线程池在 IO 密集型任务下的吞吐量 State(Scope.Benchmark) BenchmarkMode(Mode.Throughput) OutputTimeUnit(TimeUnit.SECONDS) Warmup(iterations 3, time 5) Measurement(iterations 5, time 10) Fork(1) public class VirtualThreadBenchmark { ​ // 模拟 IO 密集型任务sleep 10ms 模拟网络/数据库等待 private static final int TASK_COUNT 10000; // 每轮提交 10000 个任务 private static final long IO_DELAY_MS 10; // 每个 IO 操作 10ms ​ Benchmark Threads(1) public void platformThreadPool(Blackhole bh) throws Exception { // 平台线程池固定 200 线程典型 Web 服务器配置 ExecutorService pool Executors.newFixedThreadPool(200); try { ListFutureInteger futures new ArrayList(); for (int i 0; i TASK_COUNT; i) { futures.add(pool.submit(() - { Thread.sleep(IO_DELAY_MS); // 模拟 IO 阻塞 return 1; })); } for (FutureInteger f : futures) { bh.consume(f.get()); // 等待全部完成 } } finally { pool.shutdown(); } } ​ Benchmark Threads(1) public void virtualThreadPool(Blackhole bh) throws Exception { // 虚拟线程池每个任务一个虚拟线程不限数量 ExecutorService pool Executors.newVirtualThreadPerTaskExecutor(); try { ListFutureInteger futures new ArrayList(); for (int i 0; i TASK_COUNT; i) { futures.add(pool.submit(() - { Thread.sleep(IO_DELAY_MS); // 模拟 IO 阻塞 return 1; })); } for (FutureInteger f : futures) { bh.consume(f.get()); // 等待全部完成 } } finally { pool.shutdown(); } } }五、实测一IO 密集型任务吞吐量对比# JDK 21 运行 JMH 压测 java -Xms4g -Xmx4g -jar vtbench.jar实测结果10000 个 IO 任务每个 sleep 10ms指标平台线程池200线程虚拟线程池差异总耗时512ms106ms↓79%吞吐量任务/秒1953194339383%峰值线程数200~8载体线程载体线程极少峰值内存栈~200MB~40MB↓80%任务平均等待时间50ms1ms↓98% 实测环境Windows 11 / AMD 5800H 8核 / JDK 21.0.2 / 10000 任务每个 IO 10ms。虚拟线程吞吐量是平台线程池的 4.8 倍因为 10000 个虚拟线程可以几乎同时 sleep而 200 个平台线程要分 50 批处理。为什么差这么多平台线程池只有 200 个线程10000 个任务要分 50 批10000/200每批 10ms总耗时 500ms。虚拟线程 10000 个同时跑每个独立 sleep 10ms总耗时约 10ms调度开销≈106ms。这是阻塞不占线程带来的直接收益。六、实测二CPU 密集型任务对比虚拟线程的优势在 IO 密集型场景。CPU 密集型任务不涉及阻塞虚拟线程没有优势Benchmark Threads(1) public void cpuIntensivePlatform(Blackhole bh) throws Exception { ExecutorService pool Executors.newFixedThreadPool(8); // CPU 核心数 try { ListFutureLong futures new ArrayList(); for (int i 0; i 100; i) { final int n i; futures.add(pool.submit(() - { // 纯 CPU 计算斐波那契 long result fib(30); return result n; })); } for (FutureLong f : futures) bh.consume(f.get()); } finally { pool.shutdown(); } } ​ // 同样的 CPU 密集型任务用虚拟线程池 Benchmark Threads(1) public void cpuIntensiveVirtual(Blackhole bh) throws Exception { ExecutorService pool Executors.newVirtualThreadPerTaskExecutor(); try { ListFutureLong futures new ArrayList(); for (int i 0; i 100; i) { final int n i; futures.add(pool.submit(() - { long result fib(30); return result n; })); } for (FutureLong f : futures) bh.consume(f.get()); } finally { pool.shutdown(); } } ​ // 斐波那契递归CPU 密集型 private long fib(int n) { if (n 1) return n; return fib(n - 1) fib(n - 2); }CPU 密集型实测结果指标平台线程池8线程虚拟线程池差异吞吐量任务/秒14.214.1基本持平峰值线程数8~8相同峰值 CPU 利用率98%98%相同 结论CPU 密集型任务虚拟线程没有优势。因为 CPU 计算不阻塞虚拟线程不会让出载体线程100 个虚拟线程还是跑在 8 个载体线程上和直接用 8 线程池效果相同。虚拟线程的价值在 IO 阻塞场景。七、踩坑记录❌ 错误做法一在虚拟线程中使用 synchronized// 错误虚拟线程中使用 synchronized 会钉住载体线程JDK 21 已知问题 public synchronized String badMethod() { // synchronized 持有监视器锁时虚拟线程无法 unmount // 载体线程被钉住pin阻塞期间不能执行其他虚拟线程 return db.query(); // 这里的 IO 阻塞会钉住整个载体线程 }问题JDK 21 中synchronized块会导致虚拟线程钉住载体线程Thread Pinning阻塞期间载体线程无法释放。100 个虚拟线程进入同一个 synchronized 方法时需要 100 个载体线程失去虚拟线程的优势。✅ 正确做法// 正确用 ReentrantLock 替代 synchronizedJDK 21 虚拟线程友好 private final ReentrantLock lock new ReentrantLock(); ​ public String goodMethod() { lock.lock(); try { // ReentrantLock 的阻塞会正确触发虚拟线程 unmount return db.query(); // IO 阻塞时虚拟线程正常让出载体线程 } finally { lock.unlock(); } }原因ReentrantLock基于 AQS使用LockSupport.park阻塞JDK 21 已适配虚拟线程的 unmount/remount。synchronized基于 JVM 内置监视器JDK 21 尚未完全适配计划在后续版本修复。在虚拟线程场景下全部用ReentrantLock替代synchronized。❌ 错误做法二用虚拟线程跑 CPU 密集型长任务// 错误用虚拟线程跑长时间 CPU 计算载体线程被占满 Thread.ofVirtual().start(() - { // 纯 CPU 计算 30 秒不阻塞不释放载体线程 long result heavyCompute(30); // 载体线程被独占 30 秒 });问题虚拟线程不阻塞时不让出载体线程。8 个载体线程跑 8 个 CPU 密集虚拟线程后其他虚拟线程全部排队等待。✅ 正确做法// 正确CPU 密集型用固定大小平台线程池等于 CPU 核心数 ExecutorService cpuPool Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); cpuPool.submit(() - heavyCompute(30)); ​ // IO 密集型用虚拟线程 Thread.ofVirtual().start(() - { var data httpClient.get(url); // IO 阻塞虚拟线程优势 process(data); });原因CPU 密集型任务用平台线程池线程数 CPU 核心数避免过度竞争IO 密集型任务用虚拟线程充分利用阻塞等待时间。两者混用是最佳实践。八、常见问题Q: 虚拟线程能完全替代平台线程吗A: 不能。虚拟线程适合 IO 密集型、高并发、阻塞多的场景Web 服务器、微服务调用。平台线程适合 CPU 密集型、需要 OS 线程特性的场景JNI 调用、需要线程优先级、需要Thread.getAllStackTraces。两者可以混用——CPU 密集用平台线程池IO 密集用虚拟线程。Q: 虚拟线程和 Reactor/响应式编程WebFlux有什么区别A: 虚拟线程用同步代码风格实现异步效果——代码写法和普通线程一样JVM 底层自动处理 unmount/remount。响应式编程需要用 Mono/Flux 链式调用代码可读性差、调试困难。虚拟线程的目标就是让开发者不再需要响应式编程——同步代码 虚拟线程 异步性能。Spring Boot 3.2 已支持虚拟线程spring.threads.virtual.enabledtrue即可让 Tomcat 用虚拟线程处理请求。Q: 虚拟线程的 ThreadLocal 有什么坑A: 虚拟线程数量可能百万级每个都有独立的 ThreadLocal 副本内存可能爆炸。JDK 21 引入了ScopedValue预览特性替代 ThreadLocal——它是不可变的、作用域限定的虚拟线程结束后自动清理。如果必须用 ThreadLocal注意控制虚拟线程数量或用InheritableThreadLocal谨慎传递。总结虚拟线程的核心是用户态调度百万虚拟线程跑在少量载体线程上阻塞时自动 unmount 释放载体线程阻塞完成后 remount 继续执行。栈存在堆上按需增长每个虚拟线程只占几 KB。IO 密集型场景性能提升显著实测 10000 个 IO 任务虚拟线程吞吐量比 200 线程池高 383%内存降低 80%。CPU 密集型场景无优势不阻塞不让出载体线程。synchronized 会钉住载体线程JDK 21 中 synchronized 块导致虚拟线程无法 unmount需用 ReentrantLock 替代。这是当前版本最大的迁移成本。混用是最佳实践CPU 密集型用平台线程池线程数 CPU 核心数IO 密集型用虚拟线程两者配合发挥各自优势。 记住虚拟线程不是银弹它解决的是IO 阻塞浪费线程的问题。如果你的服务是 CPU 密集型虚拟线程帮不了你如果是 IO 密集型的高并发服务虚拟线程能让你的同步代码达到响应式编程的性能。你可能还想问Q: Spring Boot 怎么启用虚拟线程A: Spring Boot 3.2 加配置spring.threads.virtual.enabledtrueTomcat 会用虚拟线程处理每个 HTTP 请求。注意同步调用的下游服务数据库、HTTP 客户端的连接池也要相应调大否则虚拟线程会在连接池上阻塞。Q: 虚拟线程的调试和线程dump怎么看A:jstack和jcmd Thread.dump都支持虚拟线程会标注 virtual。JDK 21 的Thread.ofVirtual().start()创建的线程在 IDE 调试器中也能正常断点。注意百万虚拟线程的 thread dump 会非常大建议用jcmd pid Thread.dump_to_file -formatjson输出结构化格式。你在实际项目中用过虚拟线程吗遇到什么坑欢迎评论区交流

相关新闻

【OpenClaw从入门到精通】第84篇:为什么选择本地部署的 AI Agent 平台(超详细实战版)

【OpenClaw从入门到精通】第84篇:为什么选择本地部署的 AI Agent 平台(超详细实战版)

【OpenClaw从入门到精通】第84篇:为什么选择本地部署的 AI Agent 平台(超详细实战版) 摘要 2025年,AI Agent 不再只是 demo 里的玩具,而是逐步进入企业生产环境。然而,云端 Agent 方案(如 OpenAI Assistants、Google Vertex AI Agent Builder)在数据安全、成本可控性…

2026/7/27 5:25:11阅读更多 →
华为OD机试真题解析:几何平均值最大子数组的算法与实现

华为OD机试真题解析:几何平均值最大子数组的算法与实现

1. 项目概述:从一道真题看算法思维与工程实践最近在准备华为OD机试的朋友,估计没少被“几何平均值最大子数组”这道题刷屏。它频繁出现在各类真题汇总和备考攻略里,俨然成了检验候选人算法功底和思维缜密度的一块“试金石”。乍一看标题&…

2026/7/27 5:25:11阅读更多 →
Spring Boot 整合 Swagger2 和 Knife4j实现接口文档与可视化调试

Spring Boot 整合 Swagger2 和 Knife4j实现接口文档与可视化调试

文章目录一、Swagger2(Springfox)核心依赖与配置1.1 导入依赖1.2 配置类1.3 Spring Boot 2.6 兼容处理1.4 跨模块引用配置二、常用注解2.1 注解实例三、Knife4j 整合3.1 导入依赖3.2 配置文件3.3 访问与鉴权总结后端开发中接口文档的维护一直是痛点——代…

2026/7/27 5:25:11阅读更多 →
多模态AI怎么学?图片、文档、表格都能成为输入

多模态AI怎么学?图片、文档、表格都能成为输入

大众对人工智能的传统认知,大多局限于文字交互:使用者输入文字指令,AI返回文字答案,这种单一文字输入输出的模式,被称为单模态AI交互。但在真实学习、办公、工作场景中,绝大多数有效信息都不是纯文字形式&a…

2026/7/27 6:57:20阅读更多 →
AI基本结构11-rnn实现简单nlp

AI基本结构11-rnn实现简单nlp

循环神经网络数据准备和之前差不多,不赘述了# 一些超参数 learning_rate 1e-3 # 如果有GPU,该脚本将使用GPU进行计算 device cuda if torch.cuda.is_available() else cpu raw_datasets load_dataset(code_search_net, python) datasets raw_dataset…

2026/7/27 6:57:20阅读更多 →
AI工作流是什么?为什么比单个工具更重要

AI工作流是什么?为什么比单个工具更重要

在当前数字化办公普及的环境下,绝大多数学生与职场人员的AI使用方式,仍停留在“单次提问、单次解决问题”的浅层阶段。日常工作中遇到写文案、整理表格、简单改错、内容润色等任务,多数人都是临时输入指令、单次获取结果,用完即结…

2026/7/27 6:57:20阅读更多 →
共享屏幕怎么操作 异地共享屏幕的方法

共享屏幕怎么操作 异地共享屏幕的方法

异地对接工作、分隔两地相伴观影时,很多人都会疑惑共享屏幕怎么操作,常规共享软件存在时长限制、画面模糊等短板,普通投屏工具又只局限局域网使用,很难适配远距离场景。共享屏幕怎么操作才能兼顾流畅度与隐私保障?推荐…

2026/7/27 6:57:20阅读更多 →
华为CANN架构下Mul与Div算子的深度优化实践

华为CANN架构下Mul与Div算子的深度优化实践

1. CANN架构与算子基础解析华为CANN(Compute Architecture for Neural Networks)作为全栈AI计算架构,其核心设计理念是通过硬件-软件协同优化来释放Ascend芯片的算力潜能。在深度学习领域,基础算子如Mul(乘法&#xff…

2026/7/27 6:57:20阅读更多 →
PostgreSQL服务状态检查与故障排查指南

PostgreSQL服务状态检查与故障排查指南

1. 服务状态检查的必要性与场景解析在Linux环境下管理PostgreSQL数据库时,服务状态检查是最基础却至关重要的操作。作为数据库管理员或开发人员,我每天至少会执行5-10次状态检查命令,这就像飞行员在起飞前必须检查仪表盘一样成为肌肉记忆。典…

2026/7/27 6:55:20阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/27 1:14:34阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/27 1:14:52阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/27 1:14:56阅读更多 →
SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

1. 项目概述:从寄存器手册到实战指南 如果你手头有一份类似德州仪器(TI)TMS320x240xA系列DSP的SPI模块技术手册,看着里面密密麻麻的寄存器位定义、时序图和公式,是不是感觉头大?这份资料虽然权威&#xff0…

2026/7/27 0:00:24阅读更多 →
【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:24阅读更多 →
2007-2023年各市区县生态文明建设示范区DID

2007-2023年各市区县生态文明建设示范区DID

数据简介 自改革开放以来,我国依赖高投入、高资源消耗和高污染等传统发展模式实现了经济短期内的快速增长, 然而这也导致了严重的生态环境危机。因此,国家有力于推动企业高质量经济发展,协同生态保护的方针,从而从201…

2026/7/27 0:00:24阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/25 23:03:25阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/26 19:05:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/26 19:05:21阅读更多 →