首页/新闻资讯/正文详情

JDK 21 实战解析:虚拟线程、模式匹配与有序集合升级指南

发布时间:2026/9/27 1:12:28 来源:云帆数科 栏目:资讯中心
JDK 21 实战解析:虚拟线程、模式匹配与有序集合升级指南
JDK 21 是 Oracle 在 2023 年 9 月发布的 LTS 版本距离 JDK 17 这个上一个 LTS 已经过去了两年。我身边不少团队最近都在做 17 升 21 的评估问得最多的不是值不值得升而是虚拟线程到底能不能上生产模式匹配写起来爽不爽有序集合是不是又一个鸡肋 API。这篇就把这几个问题拆开揉碎讲清楚顺带把结构化并发、Spring Boot 3.5 启用虚拟线程这些实操细节一并交代。不管你是刚接触 JDK 21 的新手还是已经在做迁移方案的老手下面这些内容应该都能直接用上。1. 虚拟线程从线程池焦虑到一请求一线程1.1 平台线程的老问题到底卡在哪在讲虚拟线程之前得先把平台线程Platform Thread的痛点说透不然你没法理解 JDK 团队为什么要花这么大力气搞一套新的线程模型。平台线程本质上是 JVM 对操作系统内核线程的一对一封装。你new Thread()创建一个线程底层就真的去操作系统那里申请一个内核线程。这个模型有两个硬约束一是创建成本高每个线程默认要占 1MB 左右的栈空间可以通过-Xss调但调太小容易栈溢出二是数量有上限一台 8 核 16G 的机器撑死也就开几千个平台线程再多调度开销就把 CPU 吃满了。传统做法是用线程池来复用线程比如 Tomcat 默认 200 个工作线程。这个数字怎么来的其实就是经验值——200 个线程处理请求每个请求如果涉及数据库查询、RPC 调用这类 IO 操作线程会阻塞等待这时候线程池里的线程就被占着干不了别的活。所以 200 这个数字是并发请求数和IO 等待时间之间的一个妥协。问题就出在这个妥协上。假设你的接口平均响应 200ms其中 180ms 在等数据库那 200 个线程理论上每秒能处理 1000 个请求。但如果某个接口要等 2 秒的外部服务200 个线程瞬间就被打满后面的请求全部排队。你想加线程加到 500、1000上下文切换的开销又上来了而且内存也扛不住。这就是所谓的线程池焦虑——你永远在纠结池子开多大开小了扛不住突发流量开大了浪费资源还容易 OOM。1.2 虚拟线程的调度机制拆解虚拟线程Virtual Thread的核心思路是把线程这个概念从操作系统内核线程上解耦。一个虚拟线程不再对应一个内核线程而是由 JVM 自己调度运行时挂载到少量的载体线程Carrier Thread上执行。具体机制是这样的JVM 维护一个 ForkJoinPool 作为载体线程池默认大小等于 CPU 核心数。当你启动一个虚拟线程它会被提交到这个池子里由某个载体线程执行。关键在于当虚拟线程遇到阻塞操作比如Thread.sleep()、Socket读写、LockSupport.park()时JVM 会把这个虚拟线程从载体线程上卸载下来载体线程立刻去执行别的虚拟线程。等阻塞操作完成虚拟线程再被重新调度到某个载体线程上继续跑。这个卸载-挂载的过程就是虚拟线程能支撑百万级并发的根本原因。因为阻塞的时候不占载体线程所以少量载体线程就能驱动海量虚拟线程。这里有个关键点必须说清楚虚拟线程的卸载只对 JDK 层面能识别的阻塞操作生效。如果你在虚拟线程里调了一个 native 方法或者用了synchronized块包住的阻塞操作JVM 是没法卸载的这时候虚拟线程会钉住pin载体线程退化成平台线程的行为。这是虚拟线程最大的坑后面会专门讲。1.3 创建虚拟线程的几种姿势JDK 21 里创建虚拟线程有几种方式我按推荐程度排个序。最直接的是Thread.ofVirtual().start(runnable)Thread vThread Thread.ofVirtual() .name(my-virtual-thread) .start(() - { System.out.println(Running in: Thread.currentThread()); }); vThread.join();但生产环境更推荐用Executors.newVirtualThreadPerTaskExecutor()try (var executor Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i - { executor.submit(() - { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); }注意这个 Executor 是AutoCloseable的用 try-with-resources 包起来退出时会自动等待所有任务完成。这个设计很贴心避免了手动 shutdown 的麻烦。还有一种是通过Thread.ofVirtual().factory()拿到ThreadFactory然后传给Executors.newThreadPerTaskExecutor(factory)。这种方式适合你需要自定义线程工厂行为的场景。提示虚拟线程不适合池化。每个任务创建一个新虚拟线程是官方推荐的做法因为虚拟线程本身很轻量创建成本极低。用池化反而会引入不必要的复杂性。1.4 虚拟线程在 Spring Boot 3.5 里怎么启用Spring Boot 3.2 开始支持虚拟线程到 3.5 已经相当成熟了。启用方式简单到令人发指一行配置搞定spring.threads.virtual.enabledtrue这行配置做了什么事它会让 Spring Boot 自动把 Tomcat 的请求处理线程池替换成虚拟线程执行器同时Async、Scheduled这些注解背后的执行器也会切换到虚拟线程。但这里有个坑要注意启用虚拟线程后Tomcat 的最大连接数配置逻辑变了。以前server.tomcat.threads.max控制的是工作线程数现在这个配置基本失效了因为每个请求都跑在独立的虚拟线程上。真正限制并发的是server.tomcat.max-connections默认 8192。如果你有更高的并发需求得调这个参数。还有一个实测经验启用虚拟线程后数据库连接池比如 HikariCP反而成了瓶颈。因为虚拟线程让请求处理变得极快大量请求同时涌向数据库连接池瞬间被打满。这时候你需要重新评估连接池大小或者引入信号量做限流。我见过一个案例启用虚拟线程后 QPS 从 2000 涨到 8000但数据库连接池还是默认的 10 个连接结果大量请求在等连接整体响应时间反而变长了。2. 模式匹配让 instanceof 和 switch 不再啰嗦2.1 从 instanceof 的样板代码说起Java 程序员对下面这段代码肯定不陌生if (obj instanceof String) { String s (String) obj; System.out.println(s.length()); }instanceof判断完还要强制转换转换完还要声明一个新变量。这三步里类型信息重复出现了三次稍不留神改错一个地方就是 ClassCastException。JDK 16 引入了instanceof模式匹配把这三步合成一步if (obj instanceof String s) { System.out.println(s.length()); }s这个模式变量只在判断为 true 的分支里可见编译器帮你做了类型转换代码干净了不少。2.2 switch 模式匹配的完整能力JDK 21 把模式匹配扩展到了 switch这才是真正的大杀器。先看一个典型场景——处理不同形状的面积计算sealed interface Shape permits Circle, Rectangle, Triangle {} record Circle(double radius) implements Shape {} record Rectangle(double width, double height) implements Shape {} record Triangle(double base, double height) implements Shape {} double area(Shape shape) { return switch (shape) { case Circle c - Math.PI * c.radius() * c.radius(); case Rectangle r - r.width() * r.height(); case Triangle t - 0.5 * t.base() * t.height(); }; }注意最后没有default分支。因为Shape是 sealed 接口编译器知道所有可能的子类型所以能判断出 switch 已经覆盖了所有情况。这就是所谓的穷尽性检查——如果哪天你给Shape加了一个新实现编译器会直接报错逼你去处理新情况。这个特性对大型项目的可维护性提升是巨大的。模式匹配还支持守卫条件guard用when关键字String describe(Shape shape) { return switch (shape) { case Circle c when c.radius() 10 - 大圆; case Circle c - 小圆; case Rectangle r when r.width() r.height() - 正方形; case Rectangle r - 矩形; case Triangle t - 三角形; }; }when后面的表达式为 true 才会匹配这个分支这让 switch 的表达能力接近了完整的模式匹配语言。2.3 record pattern 的解构能力JDK 21 还引入了 record pattern可以直接把 record 的字段解构出来record Point(int x, int y) {} void printQuadrant(Object obj) { if (obj instanceof Point(int x, int y)) { if (x 0 y 0) System.out.println(第一象限); else if (x 0 y 0) System.out.println(第二象限); // ... } }嵌套的 record pattern 也支持record Line(Point start, Point end) {} if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) { System.out.println(线段从 ( x1 , y1 ) 到 ( x2 , y2 )); }这个解构能力配合 switch 用起来非常顺手处理 AST、JSON 树这类嵌套结构时能省掉大量 getter 调用。2.4 模式匹配在实际项目中的取舍模式匹配虽好但也不是所有场景都适合。我的经验是类型分支超过 3 个、且分支逻辑相对独立时用 switch 模式匹配最划算。如果只有一两个分支老老实实用 if-else 反而更清晰。另外要注意模式匹配和传统的常量 switch 可以混用但顺序很重要。常量分支必须放在模式分支前面否则编译器会报错。比如switch (obj) { case null - System.out.println(null); case Integer i - System.out.println(整数: i); case String s - System.out.println(字符串: s); default - System.out.println(其他); }case null是 JDK 21 新增的以前 switch 遇到 null 直接抛 NPE现在可以显式处理了。这个改动看似小但在实际项目里能省掉不少前置判空。3. 有序集合SequencedCollection 带来的统一接口3.1 为什么需要 SequencedCollectionJava 集合框架有个历史遗留问题List、Deque、SortedSet、LinkedHashSet这些集合都有顺序的概念但它们的 API 各玩各的。比如获取第一个元素List用get(0)Deque用getFirst()SortedSet用first()。你想写个通用方法处理有顺序的集合根本没法统一。JDK 21 引入了SequencedCollection、SequencedSet、SequencedMap三个接口把顺序相关的操作统一了。List和Deque现在都实现了SequencedCollectionLinkedHashSet实现了SequencedSetLinkedHashMap实现了SequencedMap。3.2 新增的 API 一览SequencedCollection新增了这些方法方法作用addFirst(E)在头部添加元素addLast(E)在尾部添加元素getFirst()获取第一个元素getLast()获取最后一个元素removeFirst()移除并返回第一个元素removeLast()移除并返回最后一个元素reversed()返回一个反转视图SequencedMap对应地增加了putFirst、putLast、firstEntry、lastEntry、pollFirstEntry、pollLastEntry、reversed()等方法。这里最值得说的是reversed()。它返回的是一个视图不是拷贝。也就是说你对反转视图的修改会反映到原集合上ListString list new ArrayList(List.of(a, b, c)); ListString reversed list.reversed(); System.out.println(reversed); // [c, b, a] reversed.add(d); // 实际加到原 list 的头部 System.out.println(list); // [d, a, b, c]这个设计很巧妙避免了拷贝开销但也意味着你得注意别在遍历反转视图的时候修改原集合。3.3 实际使用中的注意事项有序集合的 API 看起来简单但有几个坑我踩过。第一个坑是LinkedHashSet的addFirst。LinkedHashSet以前只支持尾部添加现在有了addFirst但它的实现是先把元素加到链表头部。如果你用迭代器遍历的时候调addFirst会抛ConcurrentModificationException这个和ArrayList的行为一致。第二个坑是reversed()在SortedSet上的行为。SortedSet本身就有descendingSet()方法现在又多了个reversed()。两者功能重叠但reversed()是SequencedCollection接口定义的descendingSet()是SortedSet特有的。官方建议优先用reversed()因为它是通用接口。第三个坑是关于性能的。ArrayList的addFirst是 O(n) 操作因为它要移动所有元素。如果你需要频繁在头部插入应该用ArrayDeque而不是ArrayList。这个在以前不是问题因为List根本没有addFirst现在有了这个 API反而容易让人误用。4. 结构化并发把相关任务当成一个整体4.1 结构化并发解决什么问题先看一个典型场景一个接口需要同时调用用户服务、订单服务、库存服务然后把结果聚合返回。用传统ExecutorService写出来大概是这样FutureUser userFuture executor.submit(() - userService.getUser(userId)); FutureOrder orderFuture executor.submit(() - orderService.getOrder(orderId)); FutureStock stockFuture executor.submit(() - stockService.getStock(skuId)); User user userFuture.get(); Order order orderFuture.get(); Stock stock stockService.getStock(skuId);这段代码有几个隐患如果userFuture.get()抛异常了后面两个 Future 不会被取消它们还在后台跑着浪费资源如果主线程被中断这些子任务也不会自动清理如果某个子任务卡住了你没法设置一个整体的超时。结构化并发Structured Concurrency的核心思想是把一组相关任务的生命周期绑定在一起要么全部成功要么全部失败父任务负责管理子任务的生命周期。这个思路借鉴了结构化编程里代码块有明确入口和出口的理念。4.2 StructuredTaskScope 的使用方式JDK 21 里结构化并发还是预览特性需要加--enable-preview才能用。核心 API 是StructuredTaskScopetry (var scope new StructuredTaskScope.ShutdownOnFailure()) { SubtaskUser userTask scope.fork(() - userService.getUser(userId)); SubtaskOrder orderTask scope.fork(() - orderService.getOrder(orderId)); SubtaskStock stockTask scope.fork(() - stockService.getStock(skuId)); scope.join(); // 等待所有子任务完成 scope.throwIfFailed(); // 如果有子任务失败抛出异常 return new Response(userTask.get(), orderTask.get(), stockTask.get()); }ShutdownOnFailure策略的意思是任何一个子任务失败就取消其他所有子任务。还有ShutdownOnSuccess策略用于只要有一个成功就取消其他的场景比如同时请求多个镜像源谁先返回用谁。scope.fork()返回的Subtask和Future很像但有个关键区别Subtask不能被外部取消它的生命周期完全由 scope 管理。这保证了结构化并发的结构化——子任务不会逃逸出 scope 的范围。4.3 结构化并发和虚拟线程的配合结构化并发和虚拟线程是绝配。因为虚拟线程创建成本极低你可以放心地为每个子任务创建一个虚拟线程不用担心线程池耗尽。StructuredTaskScope默认就是用虚拟线程来执行子任务的。这个组合带来的实际收益是你可以把以前串行调用三个服务的代码改成并行调用三个服务而且代码结构依然清晰异常处理和超时控制都是自动的。我实测过一个接口串行调用三个下游服务耗时 600ms改成结构化并发后降到 220ms提升接近 3 倍。注意结构化并发在 JDK 21 是预览特性生产环境使用需要谨慎评估。如果你的项目对稳定性要求极高建议等它转正后再上。JDK 23 已经把结构化并发转正了如果条件允许可以直接考虑 JDK 23。5. 升级 JDK 21 的实操避坑指南5.1 虚拟线程的 pinning 问题排查前面提到虚拟线程遇到synchronized会 pin 住载体线程这个问题在实际项目里非常常见因为大量老代码用synchronized做同步。怎么排查 pinningJVM 提供了一个系统属性-Djdk.tracePinnedThreadsfull加上这个参数后每次发生 pinning控制台会打印完整的堆栈告诉你哪个类的哪个方法导致了 pinning。我建议在测试环境先跑一轮压测把 pinning 点都找出来。修复方案通常是把synchronized换成ReentrantLock。比如// 修改前 public synchronized void doSomething() { // 阻塞操作 } // 修改后 private final ReentrantLock lock new ReentrantLock(); public void doSomething() { lock.lock(); try { // 阻塞操作 } finally { lock.unlock(); } }ReentrantLock在虚拟线程里能正常卸载不会 pin 住载体线程。但要注意不是所有synchronized都需要改只有包住了阻塞操作的才需要。如果synchronized块里全是 CPU 计算没有阻塞那 pinning 也无所谓因为载体线程本来就在干活。5.2 依赖库的兼容性检查升级 JDK 21 之前必须检查项目依赖的库是否兼容。主要关注这几类字节码操作库ASM、CGLIB、ByteBuddy 这些库如果版本太老可能不认识 JDK 21 的字节码版本65。Spring Boot 3.x 用的 CGLIB 已经内置了 ASM 9.x一般没问题但如果你项目里有老版本的字节码库需要升级。反射框架Jackson、Gson 这些序列化库通常没问题但如果用了sun.misc.Unsafe或者反射访问 JDK 内部类可能会遇到模块系统限制。JVM 参数JDK 21 移除了一些废弃的 GC 参数比如-XX:UseConcMarkSweepGC。如果你启动脚本里有这些参数JVM 会直接启动失败。我一般用jdeps工具做初步扫描jdeps --multi-release 21 --ignore-missing-deps -q your-app.jar这个命令会列出所有依赖的 JDK 内部 API如果有输出说明你的代码或依赖用到了不该用的东西。5.3 从 JDK 17 迁移的渐进式策略如果你的项目还在 JDK 17想升到 21我建议分三步走。第一步先在 JDK 21 上编译和跑单元测试不改任何代码。这一步主要暴露编译错误和测试失败通常问题不大因为 JDK 21 对 JDK 17 的兼容性很好。第二步启用虚拟线程但先不启用结构化并发。把spring.threads.virtual.enabledtrue加上然后跑集成测试和压测。重点关注数据库连接池、HTTP 客户端连接池这些共享资源是否成为瓶颈。第三步逐步引入模式匹配和有序集合的新 API。这些是纯代码层面的改进风险低可以边写新代码边用不用专门做迁移。整个过程中最需要关注的是虚拟线程带来的行为变化。比如以前依赖线程本地变量ThreadLocal传递上下文的代码在虚拟线程下可能会有问题——因为虚拟线程数量巨大ThreadLocal 如果存了大对象内存占用会飙升。JDK 21 引入了ScopedValue作为 ThreadLocal 的替代方案但目前还是预览特性可以关注一下。5.4 压测中发现的真实瓶颈最后分享一个我在实际压测中发现的坑。启用虚拟线程后应用本身的吞吐量上去了但监控系统开始报警——不是应用报错而是下游的 Redis 和 MySQL 扛不住了。原因是虚拟线程让请求处理变得太快大量请求同时打到下游下游的连接数瞬间被打满。以前用平台线程池的时候200 个线程天然起到了限流作用现在这个限流没了。解决方案有两个一是在应用层加信号量限流控制同时访问下游的请求数二是调整下游服务的连接池配置让它能承受更高的并发。我一般推荐第一种因为限流逻辑放在应用层更可控。private final Semaphore dbSemaphore new Semaphore(50); public User getUser(Long id) { dbSemaphore.acquire(); try { return userRepository.findById(id); } finally { dbSemaphore.release(); } }这个信号量的值怎么定我的经验是数据库连接池大小乘以 2 到 3。比如 HikariCP 配了 20 个连接信号量就设 40 到 60。这样既能充分利用连接池又不会让请求在连接池层面排队太久。虚拟线程不是银弹它解决的是线程数量的问题不解决下游容量的问题。启用虚拟线程之前一定要先评估下游服务的承载能力否则就是把瓶颈从应用层转移到了数据层。

相关推荐

VSCode对接Quartus Prime:告别自带编辑器,用TCL脚本打造高效FPGA开发环境
VSCode对接Quartus Prime:告别自带编辑器,用TCL脚本打造高效FPGA开发环境

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:12:28

Visio专业形状库全集:免费模具覆盖网络机房、流程、实验与办公场景
Visio专业形状库全集:免费模具覆盖网络机房、流程、实验与办公场景

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:12:28

5G信令分析实战:从SRB承载到系统消息与ODOSI排障指南
5G信令分析实战:从SRB承载到系统消息与ODOSI排障指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:12:22

车载以太网休眠唤醒验证:Kvaser Arcus三种部署形态与TC10实践
车载以太网休眠唤醒验证:Kvaser Arcus三种部署形态与TC10实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:48:52

Chrome被劫持修复指南:从原理到清理2024导航劫持木马完整方案
Chrome被劫持修复指南:从原理到清理2024导航劫持木马完整方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:48:52

基于secs4net的Secs/Gem设备主机端通信架构实践
基于secs4net的Secs/Gem设备主机端通信架构实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:48:52

VS2019 DLL动态库创建与调用配置:三步链接与避坑指南
VS2019 DLL动态库创建与调用配置:三步链接与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:48:46

中兴通讯校招薪资全拆解:岗位、城市、评级如何影响你的offer
中兴通讯校招薪资全拆解:岗位、城市、评级如何影响你的offer

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:48:40

XL2417D:2.4G高集成SoC实现300–700米低功耗无线通信
XL2417D:2.4G高集成SoC实现300–700米低功耗无线通信

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 1:48:40

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码