Java 虚拟线程是什么?对大数据开发有什么用?
面试怎么回答
平台线程和操作系统线程一一对应,每个要预留栈空间,开上万个就扛不住,线程等 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 管理的轻量线程:
- 很便宜:栈按需增长、放在堆上,创建一百万个也没问题;
- 遇到阻塞 IO 自动让出:虚拟线程执行到阻塞的 IO 操作时,JVM 把它从底层的「载体线程」上卸下来,载体线程去跑别的虚拟线程,IO 完成后再挂回去;
- 写法还是同步的:
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的这个问题。