Java 虚拟线程是什么?对大数据开发有什么用?

第 1104 题 约 3 分钟读完

本文目录
  1. 面试怎么回答
  2. 平台线程的问题
  3. 虚拟线程怎么做
  4. 在大数据开发里能用在哪
  5. 使用注意

面试怎么回答

平台线程和操作系统线程一一对应,每个要预留栈空间,开上万个就扛不住,线程等 IO 时还占着系统线程,所以只能用线程池限流或者写异步回调。JDK 21 的虚拟线程由 JVM 调度,创建成本很低,遇到阻塞 IO 会从载体线程上卸下来,让载体线程去跑别的任务,代码仍然是同步写法。大数据里适合 IO 密集、高并发的场景,比如采集同步服务、数据 API、批量调外部接口补数据;对 Spark、Flink 算子这种 CPU 密集计算没有帮助,Flink 调外部接口还是用 Async I/O。使用上不要池化虚拟线程,用信号量限流,注意 ThreadLocal 内存,以及 JDK 21 里 synchronized 导致的 Pinning 问题。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

JDK 21 正式发布了虚拟线程(Virtual Threads)。越来越多大数据组件和公司内部服务升级到 JDK 17、21,面试官会顺手问:虚拟线程是什么?你们能用上吗?

平台线程的问题

传统的 Java 线程(平台线程)和操作系统线程一一对应:

  • 每个线程默认要预留约 1MB 的栈,开几千个就很吃内存,上万个基本扛不住;
  • 线程在等 IO(查数据库、调 HTTP 接口)时什么也不干,但依然占着一个操作系统线程;
  • 所以要么用线程池限制并发,要么写回调、CompletableFuture 这类异步代码,代码难写难调。

虚拟线程怎么做

虚拟线程是 JVM 管理的轻量线程:

  1. 很便宜:栈按需增长、放在堆上,创建一百万个也没问题;
  2. 遇到阻塞 IO 自动让出:虚拟线程执行到阻塞的 IO 操作时,JVM 把它从底层的「载体线程」上卸下来,载体线程去跑别的虚拟线程,IO 完成后再挂回去;
  3. 写法还是同步的:Thread.ofVirtual().start(...) 或 Executors.newVirtualThreadPerTaskExecutor(),代码像写普通阻塞代码一样直观,却有接近异步的吞吐。

在大数据开发里能用在哪

适合大量并发、以等待 IO 为主的场景:

  • 数据采集和同步服务:同时从几百个接口、几百个库拉数据,每个任务一个虚拟线程,代码简单;
  • 元数据、数据服务类应用:数据 API、指标查询服务,每个请求要查好几个下游;
  • 批量调用外部接口做数据补全:比如对一批数据逐条调用地址解析、风控接口。

不太适合的:

  • CPU 密集型计算:Spark、Flink 的算子计算本身是吃 CPU 的,虚拟线程不会让它更快;
  • Flink、Spark 自己的线程模型由框架管理,作业代码里一般不需要自己开虚拟线程。Flink 里调外部接口,优先用 Async I/O。

使用注意

  • 不要池化虚拟线程:它本来就便宜,每个任务新建一个;要限制下游并发,用信号量而不是线程池大小;
  • 注意 ThreadLocal:虚拟线程数量巨大,往 ThreadLocal 里放大对象会占很多内存;
  • 固定(Pinning)问题:JDK 21 里在 synchronized 块中阻塞会把载体线程也卡住,高并发下要改用 ReentrantLock;JDK 24 已经解决了 synchronized 的这个问题。