从 JDK9 到 JDK21:并发编程的范式革命与虚拟线程终极指南
引言Java并发编程自诞生以来一直是开发者面临的最大挑战之一。从最初的Thread类和Runnable接口到JDK5引入的java.util.concurrent包再到JDK8的CompletableFuture和Stream APIJava一直在不断演进以满足现代应用对高并发的需求。JDK9到JDK21的发布标志着Java并发编程进入了一个全新的时代。特别是JDK21中正式转正的虚拟线程Virtual Thread彻底改变了Java并发编程的范式解决了困扰Java开发者多年的平台线程资源瓶颈问题。一、JDK9-JDK21并发编程核心新特性全景1.1 JDK9并发编程的基础重构JDK9是Java模块化系统的第一个版本同时也对并发编程的基础进行了大量重构和优化。1.1.1 CompletableFuture的增强JDK9为CompletableFuture增加了9个新方法极大地提升了其表达能力和灵活性。package com.jam.demo.concurrent; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * JDK9 CompletableFuture增强示例 * * author ken * date 2026-04-16 */ Slf4j public class CompletableFutureEnhanceDemo { private static final ExecutorService executor Executors.newFixedThreadPool(4); /** * 演示completeOnTimeout方法 * 如果在指定时间内没有完成使用默认值完成 */ public static void demoCompleteOnTimeout() { CompletableFutureString future CompletableFuture.supplyAsync(() - { try { TimeUnit.SECONDS.sleep(2); return 实际结果; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return 中断结果; } }, executor); String result future.completeOnTimeout(超时默认值, 1, TimeUnit.SECONDS) .join(); log.info(completeOnTimeout结果: {}, result); } /** * 演示orTimeout方法 * 如果在指定时间内没有完成抛出TimeoutException */ public static void demoOrTimeout() { CompletableFutureString future CompletableFuture.supplyAsync(() - { try { TimeUnit.SECONDS.sleep(2); return 实际结果; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return 中断结果; } }, executor); try { future.orTimeout(1, TimeUnit.SECONDS).join(); } catch (Exception e) { log.error(orTimeout抛出异常, e); } } /** * 演示failedFuture方法 * 创建一个已经异常完成的CompletableFuture */ public static void demoFailedFuture() { CompletableFutureString future CompletableFuture.failedFuture( new RuntimeException(预定义异常)); try { future.join(); } catch (Exception e) { log.error(failedFuture抛出异常, e); } } /** * 演示delayedExecutor方法 * 创建一个延迟执行任务的Executor */ public static void demoDelayedExecutor() { CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS, executor) .execute(() - log.info(延迟1秒执行的任务)); } public static void main(String[] args) { demoCompleteOnTimeout(); demoOrTimeout(); demoFailedFuture(); demoDelayedExecutor(); executor.shutdown(); } }1.1.2 Flow API响应式编程的标准JDK9引入了java.util.concurrent.Flow接口定义了响应式流Reactive Streams的标准规范。这是Java官方对响应式编程的首次支持为后续Spring WebFlux等框架提供了统一的基础。package com.jam.demo.concurrent; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Flow; import java.util.concurrent.SubmissionPublisher; import java.util.concurrent.TimeUnit; /** * JDK9 Flow API示例 * * author ken * date 2026-04-16 */ Slf4j public class FlowApiDemo { /** * 自定义订阅者 */ static class SimpleSubscriberT implements Flow.SubscriberT { private Flow.Subscription subscription; private final String name; public SimpleSubscriber(String name) { this.name name; } Override public void onSubscribe(Flow.Subscription subscription) { this.subscription subscription; log.info({}: 订阅成功, name); subscription.request(1); } Override public void onNext(T item) { log.info({}: 收到消息: {}, name, item); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } subscription.request(1); } Override public void onError(Throwable throwable) { log.error({}: 发生错误, name, throwable); } Override public void onComplete() { log.info({}: 数据流结束, name); } } public static void main(String[] args) throws InterruptedException { try (SubmissionPublisherString publisher new SubmissionPublisher()) { publisher.subscribe(new SimpleSubscriber(订阅者1)); publisher.subscribe(new SimpleSubscriber(订阅者2)); for (int i 1; i 5; i) { publisher.submit(消息- i); log.info(发布消息: 消息-{}, i); } } TimeUnit.SECONDS.sleep(1); } }1.1.3 VarHandle变量句柄VarHandle提供了对变量的细粒度原子操作支持替代了Unsafe类的大部分功能。它支持对对象字段、数组元素的各种原子操作包括get、set、compareAndSet、getAndAdd等。package com.jam.demo.concurrent; import lombok.extern.slf4j.Slf4j; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.util.concurrent.CountDownLatch; /** * JDK9 VarHandle示例 * * author ken * date 2026-04-16 */ Slf4j public class VarHandleDemo { private volatile int count 0; private static final VarHandle COUNT_HANDLE; static { try { COUNT_HANDLE MethodHandles.lookup() .findVarHandle(VarHandleDemo.class, count, int.class); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } /** * 使用VarHandle进行原子递增 */ public void increment() { COUNT_HANDLE.getAndAdd(this, 1); } /** * 使用VarHandle进行CAS操作 */ public boolean compareAndSet(int expect, int update) { return COUNT_HANDLE.compareAndSet(this, expect, update); } public static void main(String[] args) throws InterruptedException { VarHandleDemo demo new VarHandleDemo(); CountDownLatch latch new CountDownLatch(10); for (int i 0; i 10; i) { new Thread(() - { for (int j 0; j 1000; j) { demo.increment(); } latch.countDown(); }).start(); } latch.await(); log.info(最终计数: {}, demo.count); boolean success demo.compareAndSet(10000, 20000); log.info(CAS操作结果: {}, 计数: {}, success, demo.count); } }1.2 JDK10局部变量类型推断JDK10引入了局部变量类型推断var关键字虽然这不是专门为并发编程设计的特性但它极大地简化了并发代码的编写提高了代码的可读性。package com.jam.demo.concurrent; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * JDK10 var关键字在并发编程中的应用 * * author ken * date 2026-04-16 */ Slf4j public class VarInConcurrentDemo { public static void main(String[] args) { var executor Executors.newFixedThreadPool(4); var future1 CompletableFuture.supplyAsync(() - Hello, executor); var future2 CompletableFuture.supplyAsync(() - World, executor); var combinedFuture future1.thenCombine(future2, (s1, s2) - s1 s2); combinedFuture.thenAccept(result - log.info(结果: {}, result)); executor.shutdown(); } }1.3 JDK11HttpClient APIJDK11将HttpClient API正式转正替代了老旧的HttpURLConnection。新的HttpClient完全支持异步非阻塞操作基于CompletableFuture实现非常适合高并发的网络请求场景。package com.jam.demo.concurrent; import lombok.extern.slf4j.Slf4j; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * JDK11 HttpClient API示例 * * author ken * date 2026-04-16 */ Slf4j public class HttpClientDemo { private static final HttpClient httpClient HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .connectTimeout(Duration.ofSeconds(10)) .build(); /** * 同步GET请求 */ public static void syncGet() throws Exception { HttpRequest request HttpRequest.newBuilder() .uri(URI.create(https://httpbin.org/get)) .timeout(Duration.ofSeconds(5)) .header(Accept, application/json) .GET() .build(); HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); log.info(同步请求状态码: {}, response.statusCode()); log.info(同步请求响应: {}, response.body()); } /** * 异步GET请求 */ public static CompletableFutureString asyncGet(String url) { HttpRequest request HttpRequest.newBuilder() .uri(URI.create(url)) .timeout(Duration.ofSeconds(5)) .header(Accept, application/json) .GET() .build(); return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body); } /** * 批量异步请求 */ public static void batchAsyncRequests() { ListCompletableFutureString futures IntStream.range(1, 6) .mapToObj(i - asyncGet(https://httpbin.org/get?param i)) .collect(Collectors.toList()); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenRun(() - { ListString results futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); log.info(批量请求完成共收到{}个响应, results.size()); }) .join(); } public static void main(String[] args) throws Exception { syncGet(); batchAsyncRequests(); } }1.4 JDK14Record类JDK14引入了Record类用于创建不可变的数据载体。在并发编程中不可变对象是线程安全的因此Record类非常适合在多线程环境中传递数据。package com.jam.demo.concurrent; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * JDK14 Record类在并发编程中的应用 * * author ken * date 2026-04-16 */ Slf4j public class RecordInConcurrentDemo { /** * 用户信息Record不可变线程安全 */ public record User(Long id, String username, String email) {} /** * 异步获取用户信息 */ public static CompletableFutureUser getUserById(Long userId) { return CompletableFuture.supplyAsync(() - { // 模拟数据库查询 return new User(userId, user userId, user userId example.com); }); } public static void main(String[] args) { ExecutorService executor Executors.newFixedThreadPool(4); CompletableFuture.allOf( getUserById(1L).thenAccept(user - log.info(用户1: {}, user)), getUserById(2L).thenAccept(user - log.info(用户2: {}, user)), getUserById(3L).thenAccept(user - log.info(用户3: {}, user)) ).join(); executor.shutdown(); } }1.5 JDK16Stream API增强JDK16对Stream API进行了增强增加了toList()、mapMulti()等方法简化了并发流的操作。package com.jam.demo.concurrent; import lombok.extern.slf4j.Slf4j; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * JDK16 Stream API增强示例 * * author ken * date 2026-04-16 */ Slf4j public class StreamEnhanceDemo { /** * 演示toList()方法 * 返回不可变列表 */ public static void demoToList() { ListInteger list IntStream.range(1, 10) .parallel() .filter(i - i % 2 0) .boxed() .toList(); log.info(toList结果: {}, list); } /** * 演示mapMulti()方法 * 替代flatMap性能更好 */ public static void demoMapMulti() { ListInteger list IntStream.range(1, 5) .parallel() .IntegermapMulti((num, consumer) - { consumer.accept(num); consumer.accept(num * 2); }) .boxed() .collect(Collectors.toList()); log.info(mapMulti结果: {}, list); } public static void main(String[] args) { demoToList(); demoMapMulti(); } }1.6 JDK17ZGC正式版JDK17将ZGC垃圾回收器正式转正。ZGC是一款低延迟垃圾回收器其暂停时间不超过10毫秒并且不会随着堆大小的增加而增加。这对于高并发、低延迟要求的应用来说是一个巨大的福音。ZGC的主要特点亚毫秒级暂停时间支持TB级堆内存并发执行大部分GC工作对应用吞吐量影响小1.7 JDK19虚拟线程预览版JDK19引入了虚拟线程的第一个预览版这是Java并发编程史上最重要的变革之一。虚拟线程是轻量级的线程由JVM管理而不是操作系统。一个平台线程可以运行成千上万个虚拟线程极大地提高了并发能力。1.8 JDK20虚拟线程第二次预览JDK20对虚拟线程进行了改进和优化解决了一些在第一个预览版中发现的问题并增加了一些新的API。1.9 JDK21虚拟线程正式版JDK21将虚拟线程正式转正标志着Java并发编程进入了一个全新的时代。同时JDK21还引入了结构化并发Structured Concurrency的预览版进一步简化了并发编程的复杂性。二、虚拟线程深度解析2.1 什么是虚拟线程虚拟线程是JVM管理的轻量级线程也被称为用户态线程或纤程。与平台线程Platform Thread不同虚拟线程不是操作系统内核线程的一对一映射而是由JVM在用户态进行调度和管理。一个平台线程可以承载成千上万个虚拟线程的执行。当虚拟线程执行阻塞操作时JVM会自动将其从平台线程上卸载让平台线程去执行其他虚拟线程。这样就避免了平台线程因阻塞而浪费资源的问题。2.2 平台线程vs虚拟线程平台线程和虚拟线程的核心区别如下表所示特性平台线程虚拟线程调度者操作系统内核JVM上下文切换内核态切换开销大用户态切换开销小内存占用每个线程栈默认1MB初始栈大小几百字节数量限制通常几千个可以达到百万级创建成本高极低阻塞行为阻塞整个平台线程仅阻塞虚拟线程池化必要性必须池化不需要池化2.3 虚拟线程的底层实现原理2.3.1 调度模型虚拟线程采用了M:N的调度模型即M个虚拟线程映射到N个平台线程上执行。JDK中的虚拟线程实现基于ForkJoinPool默认使用一个大小等于CPU核心数的ForkJoinPool作为调度器。当虚拟线程需要执行时它会被提交到调度器的任务队列中。调度器会将任务分配给空闲的平台线程执行。当虚拟线程执行阻塞操作时JVM会保存其执行上下文并将平台线程释放出来执行其他虚拟线程。当阻塞操作完成后虚拟线程会被重新提交到调度器的任务队列中等待再次被调度执行。2.3.2 栈管理虚拟线程的栈是分段式的由多个栈帧块组成。每个栈帧块的大小通常是4KB。当虚拟线程的栈需要增长时JVM会动态分配新的栈帧块。当栈收缩时JVM会回收不再使用的栈帧块。这种分段式栈的设计使得虚拟线程的初始内存占用非常小只有几百字节。同时它也支持栈的动态增长和收缩提高了内存利用率。2.3.3 阻塞处理这是虚拟线程最核心的特性。当虚拟线程执行以下阻塞操作时JVM会自动进行挂载和卸载Thread.sleep()Object.wait()LockSupport.park()java.util.concurrent.locks.Lock.lock()java.nio.channels.InterruptibleChannel的IO操作java.net.Socket的IO操作当虚拟线程执行这些阻塞操作时JVM会保存虚拟线程的执行上下文将虚拟线程从平台线程上卸载让平台线程去执行其他虚拟线程当阻塞操作完成后将虚拟线程重新提交到调度器调度器会将虚拟线程分配给某个平台线程继续执行需要注意的是并不是所有的阻塞操作都会触发虚拟线程的卸载。如果阻塞操作是在本地方法中执行的或者是通过JNI调用的C代码中的阻塞操作那么JVM无法拦截此时会阻塞整个平台线程。2.3.4 上下文切换虚拟线程的上下文切换是在用户态进行的不需要进入内核态。这使得虚拟线程的上下文切换开销比平台线程小得多。平台线程的上下文切换需要保存和恢复CPU的所有寄存器状态并且需要切换页表这些操作都需要在内核态完成开销很大。而虚拟线程的上下文切换只需要保存和恢复虚拟线程自己的执行上下文这些操作都在用户态完成开销很小。2.4 虚拟线程的生命周期虚拟线程的生命周期与平台线程类似包括以下几个状态NEW新建状态线程已创建但未启动RUNNABLE可运行状态正在执行或等待CPU时间BLOCKED阻塞状态等待获取锁WAITING等待状态等待其他线程的通知TIMED_WAITING计时等待状态TERMINATED终止状态线程已执行完毕三、虚拟线程实战3.1 创建虚拟线程JDK21提供了多种创建虚拟线程的方式package com.jam.demo.virtual; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; /** * 虚拟线程创建示例 * * author ken * date 2026-04-16 */ Slf4j public class VirtualThreadCreationDemo { public static void main(String[] args) throws InterruptedException { // 方式1使用Thread.startVirtualThread() Thread vThread1 Thread.startVirtualThread(() - { log.info(虚拟线程1正在执行); }); vThread1.join(); // 方式2使用Thread.Builder Thread vThread2 Thread.ofVirtual() .name(virtual-thread-2) .unstarted(() - { log.info(虚拟线程2正在执行); }); vThread2.start(); vThread2.join(); // 方式3创建线程工厂 ThreadFactory factory Thread.ofVirtual() .name(virtual-thread-, 3) .factory(); Thread vThread3 factory.newThread(() - { log.info(虚拟线程3正在执行); }); vThread3.start(); vThread3.join(); // 方式4使用Executors.newVirtualThreadPerTaskExecutor() try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 4; i 6; i) { int finalI i; executor.submit(() - { log.info(虚拟线程{}正在执行, finalI); }); } } } }3.2 虚拟线程的基本使用下面是一个简单的虚拟线程使用示例展示了如何使用虚拟线程执行大量并发任务package com.jam.demo.virtual; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * 虚拟线程基本使用示例 * * author ken * date 2026-04-16 */ Slf4j public class VirtualThreadBasicDemo { public static void main(String[] args) throws InterruptedException { AtomicInteger counter new AtomicInteger(0); long startTime System.currentTimeMillis(); // 创建10000个虚拟线程 try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 0; i 10000; i) { executor.submit(() - { try { // 模拟IO操作 TimeUnit.MILLISECONDS.sleep(10); counter.incrementAndGet(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } } long endTime System.currentTimeMillis(); log.info(执行完成共处理{}个任务耗时{}ms, counter.get(), endTime - startTime); } }3.3 虚拟线程与平台线程性能对比下面我们通过一个简单的测试来对比虚拟线程和平台线程的性能package com.jam.demo.virtual; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * 虚拟线程与平台线程性能对比 * * author ken * date 2026-04-16 */ Slf4j public class ThreadPerformanceComparison { private static final int TASK_COUNT 10000; private static final int SLEEP_TIME 10; /** * 使用平台线程执行任务 */ public static void testPlatformThreads() throws InterruptedException { AtomicInteger counter new AtomicInteger(0); long startTime System.currentTimeMillis(); // 使用固定大小的线程池 try (ExecutorService executor Executors.newFixedThreadPool(200)) { for (int i 0; i TASK_COUNT; i) { executor.submit(() - { try { TimeUnit.MILLISECONDS.sleep(SLEEP_TIME); counter.incrementAndGet(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } } long endTime System.currentTimeMillis(); log.info(平台线程: 处理{}个任务耗时{}ms, counter.get(), endTime - startTime); } /** * 使用虚拟线程执行任务 */ public static void testVirtualThreads() throws InterruptedException { AtomicInteger counter new AtomicInteger(0); long startTime System.currentTimeMillis(); try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 0; i TASK_COUNT; i) { executor.submit(() - { try { TimeUnit.MILLISECONDS.sleep(SLEEP_TIME); counter.incrementAndGet(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } } long endTime System.currentTimeMillis(); log.info(虚拟线程: 处理{}个任务耗时{}ms, counter.get(), endTime - startTime); } public static void main(String[] args) throws InterruptedException { // 预热 testVirtualThreads(); testPlatformThreads(); // 正式测试 testPlatformThreads(); testVirtualThreads(); } }在典型的测试环境中虚拟线程的执行速度会比平台线程快5-10倍。这是因为平台线程池的大小有限大部分时间都在等待线程空闲而虚拟线程可以同时执行所有任务。3.4 虚拟线程中的锁虚拟线程支持所有Java的锁机制包括synchronized关键字和java.util.concurrent.locks包中的锁。但是在虚拟线程中使用synchronized关键字会有一些特殊的行为。当虚拟线程持有synchronized锁时它不会被卸载。也就是说如果一个虚拟线程在持有synchronized锁的情况下执行了阻塞操作那么它会阻塞整个平台线程。这是因为synchronized锁是基于操作系统的监视器实现的JVM无法拦截这种情况下的阻塞。为了解决这个问题建议在虚拟线程中使用java.util.concurrent.locks.ReentrantLock代替synchronized关键字。ReentrantLock是在Java层面实现的JVM可以拦截其阻塞操作并正确地卸载虚拟线程。package com.jam.demo.virtual; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 虚拟线程中的锁使用示例 * * author ken * date 2026-04-16 */ Slf4j public class VirtualThreadLockDemo { private static final Lock reentrantLock new ReentrantLock(); private static final Object syncLock new Object(); private static int counter 0; /** * 使用ReentrantLock推荐在虚拟线程中使用 */ public static void incrementWithReentrantLock() { reentrantLock.lock(); try { counter; TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { reentrantLock.unlock(); } } /** * 使用synchronized不推荐在虚拟线程中使用 */ public static void incrementWithSync() { synchronized (syncLock) { counter; try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) throws InterruptedException { counter 0; long startTime System.currentTimeMillis(); try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 0; i 10000; i) { executor.submit(VirtualThreadLockDemo::incrementWithReentrantLock); } } long endTime System.currentTimeMillis(); log.info(使用ReentrantLock: 计数{}, 耗时{}ms, counter, endTime - startTime); counter 0; startTime System.currentTimeMillis(); try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 0; i 10000; i) { executor.submit(VirtualThreadLockDemo::incrementWithSync); } } endTime System.currentTimeMillis(); log.info(使用synchronized: 计数{}, 耗时{}ms, counter, endTime - startTime); } }3.5 虚拟线程中的ThreadLocal虚拟线程支持ThreadLocal并且在虚拟线程中使用ThreadLocal比在平台线程中更加高效。这是因为虚拟线程的ThreadLocal是存储在虚拟线程对象本身中的而不是存储在一个全局的映射表中。但是由于虚拟线程的数量可以非常大使用ThreadLocal时需要注意内存泄漏问题。如果虚拟线程长时间存在并且ThreadLocal中存储了大量数据那么可能会导致内存溢出。package com.jam.demo.virtual; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 虚拟线程中的ThreadLocal使用示例 * * author ken * date 2026-04-16 */ Slf4j public class VirtualThreadThreadLocalDemo { private static final ThreadLocalString USER_CONTEXT new ThreadLocal(); /** * 设置用户上下文 */ public static void setUserContext(String username) { USER_CONTEXT.set(username); } /** * 获取用户上下文 */ public static String getUserContext() { return USER_CONTEXT.get(); } /** * 清除用户上下文 */ public static void clearUserContext() { USER_CONTEXT.remove(); } public static void main(String[] args) { try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 1; i 5; i) { int finalI i; executor.submit(() - { try { setUserContext(user finalI); log.info(虚拟线程{}: 用户上下文{}, finalI, getUserContext()); } finally { clearUserContext(); } }); } } } }四、虚拟线程的适用场景与不适用场景4.1 适用场景虚拟线程特别适合以下场景IO密集型应用Web应用服务器微服务数据库访问网络通信文件操作在这些场景中大部分时间都在等待IO操作完成。使用虚拟线程可以同时处理大量的请求而不会因为平台线程数量的限制而导致性能瓶颈。高并发的任务处理消息队列消费者批处理任务定时任务爬虫这些场景通常需要同时处理大量的任务使用虚拟线程可以极大地提高系统的吞吐量。简化并发编程替代复杂的异步编程模型简化错误处理提高代码的可读性和可维护性虚拟线程允许开发者使用同步的编程风格来编写异步的代码避免了回调地狱和CompletableFuture的复杂性。4.2 不适用场景虚拟线程不适合以下场景CPU密集型应用科学计算图像处理视频编码在这些场景中CPU是瓶颈增加线程数量并不能提高性能。使用虚拟线程反而会增加调度开销。长时间持有synchronized锁的场景如前所述当虚拟线程持有synchronized锁时它不会被卸载。如果长时间持有synchronized锁会导致平台线程被阻塞降低系统的并发能力。执行本地方法或JNI调用的场景如果阻塞操作是在本地方法中执行的JVM无法拦截会阻塞整个平台线程。需要精确控制线程优先级的场景虚拟线程不支持线程优先级所有虚拟线程的优先级都是相同的。五、生产环境落地避坑方案5.1 不要池化虚拟线程这是使用虚拟线程最常见的错误。平台线程因为创建成本高所以需要池化。但是虚拟线程的创建成本非常低池化虚拟线程不仅没有必要反而会带来很多问题。错误的做法// 错误不要池化虚拟线程 ExecutorService executor new ThreadPoolExecutor( 100, 100, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), Thread.ofVirtual().factory() );正确的做法// 正确为每个任务创建一个新的虚拟线程 try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { // 提交任务 }5.2 避免长时间持有synchronized锁如前所述在虚拟线程中长时间持有synchronized锁会导致平台线程被阻塞。建议使用ReentrantLock代替synchronized关键字。如果必须使用synchronized关键字应该尽量缩小锁的范围避免在持有锁的情况下执行阻塞操作。5.3 注意ThreadLocal的使用由于虚拟线程的数量可以非常大使用ThreadLocal时需要注意内存泄漏问题。应该在使用完ThreadLocal后及时调用remove()方法清除数据。另外不要在ThreadLocal中存储大量数据否则可能会导致内存溢出。5.4 避免在虚拟线程中执行CPU密集型任务虚拟线程适合执行IO密集型任务不适合执行CPU密集型任务。如果有CPU密集型任务应该使用专门的平台线程池来执行。package com.jam.demo.virtual; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * CPU密集型任务处理示例 * * author ken * date 2026-04-16 */ Slf4j public class CpuIntensiveTaskDemo { // 专门用于执行CPU密集型任务的平台线程池 private static final ExecutorService CPU_EXECUTOR Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); /** * CPU密集型任务 */ public static long cpuIntensiveTask(long n) { long result 0; for (long i 0; i n; i) { result i; } return result; } public static void main(String[] args) { try (ExecutorService virtualExecutor Executors.newVirtualThreadPerTaskExecutor()) { for (int i 0; i 10; i) { virtualExecutor.submit(() - { // IO操作 log.info(执行IO操作); // 将CPU密集型任务提交给专门的线程池 long result CPU_EXECUTOR.submit(() - cpuIntensiveTask(100000000)) .get(); log.info(CPU密集型任务结果: {}, result); }); } } CPU_EXECUTOR.shutdown(); } }5.5 配置合适的调度器参数虚拟线程的默认调度器是一个大小等于CPU核心数的ForkJoinPool。在大多数情况下这个默认配置是合适的。但是在某些特殊情况下可能需要调整调度器的参数。可以通过以下系统属性来配置虚拟线程的调度器jdk.virtualThreadScheduler.parallelism调度器的并行度默认等于CPU核心数jdk.virtualThreadScheduler.maxPoolSize调度器的最大线程数默认是256jdk.virtualThreadScheduler.minRunnable调度器保持的最小可运行线程数默认是15.6 监控和调优在生产环境中使用虚拟线程时需要进行适当的监控和调优。可以使用JDK自带的工具如jconsole、jvisualvm来监控虚拟线程的运行情况。另外JDK21提供了一些新的JMX指标来监控虚拟线程java.lang:typeVirtualThread虚拟线程的总数java.lang:typeVirtualThreadScheduler调度器的运行情况5.7 兼容性问题在将现有应用迁移到虚拟线程时需要注意兼容性问题。一些旧的库可能假设线程是平台线程并且依赖于平台线程的某些特性。特别是以下情况需要特别注意依赖于线程ID的库依赖于线程优先级的库依赖于线程组的库使用ThreadLocal作为缓存的库六、结构化并发JDK21引入了结构化并发Structured Concurrency的预览版这是一种新的并发编程范式旨在简化并发编程的复杂性并提高代码的可靠性。结构化并发的核心思想是并发任务的生命周期应该与代码块的生命周期一致。当代码块执行完毕时所有在该代码块中启动的并发任务都应该已经完成。package com.jam.demo.virtual; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.StructuredTaskScope; import java.util.concurrent.TimeUnit; /** * 结构化并发示例 * * author ken * date 2026-04-16 */ Slf4j public class StructuredConcurrencyDemo { /** * 模拟用户服务 */ public static String getUser(Long userId) throws InterruptedException { TimeUnit.MILLISECONDS.sleep(100); return User: userId; } /** * 模拟订单服务 */ public static String getOrder(Long orderId) throws InterruptedException { TimeUnit.MILLISECONDS.sleep(150); return Order: orderId; } /** * 使用结构化并发同时获取用户和订单信息 */ public static void fetchUserAndOrder(Long userId, Long orderId) throws Exception { try (var scope new StructuredTaskScope.ShutdownOnFailure()) { var userFuture scope.fork(() - getUser(userId)); var orderFuture scope.fork(() - getOrder(orderId)); scope.join().throwIfFailed(); String user userFuture.get(); String order orderFuture.get(); log.info(用户: {}, 订单: {}, user, order); } } public static void main(String[] args) throws Exception { fetchUserAndOrder(1L, 100L); } }结构化并发的主要优点自动错误传播如果任何一个任务失败所有其他任务都会被取消自动取消当代码块执行完毕时所有未完成的任务都会被取消简化错误处理不需要手动处理多个CompletableFuture的异常提高代码的可读性和可维护性七、总结与展望JDK9到JDK21的发布为Java并发编程带来了革命性的变化。特别是虚拟线程的正式转正彻底解决了困扰Java开发者多年的平台线程资源瓶颈问题。虚拟线程允许开发者使用同步的编程风格来编写高并发的应用极大地简化了并发编程的复杂性。同时它也提供了比传统平台线程高得多的性能和吞吐量。但是虚拟线程并有自己的适用场景和局限性。在使用虚拟线程时需要注意避免一些常见的陷阱如池化虚拟线程、长时间持有synchronized锁等。