JDK21 升级之 虚拟线程

667 阅读6分钟

虚拟线程(Virtual Threads)简介

虚拟线程是 JDK 19 引入的预览特性,并在 JDK 21 中成为正式特性。它是 Java 平台线程模型的一次重大改进,旨在解决传统线程在高并发场景下的性能和资源限制问题。


虚拟线程与普通线程的区别

特性普通线程(Platform Threads)虚拟线程(Virtual Threads)
实现方式映射到操作系统线程(OS Thread)由 JVM 管理,轻量级线程
线程数量限制受限于操作系统资源(如内存、线程数)几乎无限制,支持数百万级线程
阻塞操作阻塞操作会占用操作系统线程阻塞操作会挂起虚拟线程,不占用 OS 线程
上下文切换依赖操作系统,开销较大JVM 内部管理,开销更小
适用场景适合中小规模并发适合大规模并发(如高并发 Web 服务)
线程栈每个线程分配固定大小的内存栈栈按需分配,内存利用率更高
调试和监控使用传统工具(如 JStack)与普通线程一致,工具兼容性好

虚拟线程的好处

  1. 高并发性能

    • 虚拟线程是轻量级的,可以在 JVM 中创建数百万个线程,而不会耗尽系统资源。
    • 适合高并发场景,如 Web 服务、微服务和实时数据处理。
  2. 简化异步编程

    • 虚拟线程支持阻塞式编程模型,开发者可以直接使用同步代码风格,而不需要复杂的异步回调或 CompletableFuture
  3. 更高的资源利用率

    • 虚拟线程的栈按需分配,内存利用率更高。
    • 阻塞操作不会占用操作系统线程,释放了更多资源。
  4. 与现有工具兼容

    • 虚拟线程与普通线程共享相同的调试和监控工具(如 JStack、JVisualVM)。

代码对比:普通线程 vs 虚拟线程

1. 使用普通线程实现高并发
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PlatformThreadsExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10); // 固定线程池

        for (int i = 0; i < 100; i++) {
            executor.submit(() -> {
                System.out.println("Running in platform thread: " + Thread.currentThread());
                try {
                    Thread.sleep(1000); // 模拟阻塞操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        executor.shutdown();
    }
}

问题

  • 线程池大小有限(这里是 10),无法同时处理大量任务。
  • 阻塞操作(如 Thread.sleep)会占用操作系统线程,降低资源利用率。

2. 使用虚拟线程实现高并发
import java.util.concurrent.Executors;

public class VirtualThreadsExample {
    public static void main(String[] args) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { // 虚拟线程池
            for (int i = 0; i < 100; i++) {
                executor.submit(() -> {
                    System.out.println("Running in virtual thread: " + Thread.currentThread());
                    try {
                        Thread.sleep(1000); // 模拟阻塞操作
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } // 自动关闭虚拟线程池
    }
}

优势

  • 每个任务都运行在独立的虚拟线程中,支持数百万级并发。
  • 阻塞操作(如 Thread.sleep)不会占用操作系统线程,资源利用率更高。

虚拟线程的使用案例

1. 高并发 HTTP 服务器

使用虚拟线程处理每个 HTTP 请求:

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;

public class VirtualThreadHttpServer {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/", exchange -> {
            try (exchange) {
                String response = "Hello, Virtual Threads!";
                exchange.sendResponseHeaders(200, response.getBytes().length);
                exchange.getResponseBody().write(response.getBytes());
            }
        });

        server.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); // 使用虚拟线程池
        server.start();
        System.out.println("Server started on port 8080");
    }
}

优势

  • 每个请求由独立的虚拟线程处理,支持高并发。
  • 简化了异步编程模型,代码风格更接近同步编程。

2. 并发任务处理

使用虚拟线程并发执行多个任务:

import java.util.concurrent.Executors;

public class VirtualThreadTaskExample {
    public static void main(String[] args) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 1000; i++) {
                executor.submit(() -> {
                    System.out.println("Task executed by: " + Thread.currentThread());
                });
            }
        }
    }
}

优势

  • 可以轻松创建数千个并发任务,而不会耗尽系统资源。

总结

虚拟线程的主要优势
  1. 高并发性能:支持数百万级线程,适合高并发场景。
  2. 简化编程模型:支持阻塞式编程风格,减少异步编程的复杂性。
  3. 高效资源利用:阻塞操作不会占用操作系统线程,内存利用率更高。
  4. 工具兼容性:与普通线程共享相同的调试和监控工具。
适用场景
  • 高并发 Web 服务。
  • 实时数据处理。
  • 并发任务执行。

SpringBoot Undertow 使用虚拟线程

以下是一个 Spring Boot 项目,使用 Undertow 作为 Web 容器,结合 Spring WebFlux 和 JDK 21 虚拟线程 的示例。


项目说明

  1. Web 容器:使用 Undertow 替代默认的 Tomcat。
  2. 反应式框架:使用 Spring WebFlux 处理异步请求。
  3. 虚拟线程:通过虚拟线程处理每个请求,提升高并发性能。

完整示例代码

1. Maven 依赖

在 pom.xml 中添加以下依赖:

<dependencies>
    <!-- Spring Boot Starter for WebFlux -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

    <!-- Undertow Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
</dependencies>
2. 配置 Undertow 使用虚拟线程

在 Spring Boot 中,可以通过自定义 Undertow 的线程池来使用虚拟线程。

import io.undertow.UndertowOptions;
import org.springframework.boot.web.embedded.undertow.UndertowBuilderCustomizer;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executors;

@Configuration
public class UndertowConfig {

    @Bean
    public WebServerFactoryCustomizer<UndertowServletWebServerFactory> undertowCustomizer() {
        return factory -> factory.addBuilderCustomizers((UndertowBuilderCustomizer) builder -> {
            // 使用虚拟线程作为 Undertow 的线程池
            builder.setWorkerOption(UndertowOptions.WORKER_TASK_CORE_THREADS, 0);
            builder.setWorkerOption(UndertowOptions.WORKER_TASK_MAX_THREADS, Integer.MAX_VALUE);
            builder.setWorkerExecutor(Executors.newVirtualThreadPerTaskExecutor());
        });
    }
}
3. 创建 WebFlux 控制器

使用 Spring WebFlux 的 Mono 和 Flux 处理异步请求。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
public class VirtualThreadController {

    @GetMapping("/virtual-thread")
    public Mono<String> handleRequest() {
        return Mono.fromCallable(() -> {
            System.out.println("Handling request in thread: " + Thread.currentThread());
            Thread.sleep(1000); // 模拟阻塞操作
            return "Handled by virtual thread!";
        });
    }
}
4. 主应用程序

启动 Spring Boot 应用程序。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class VirtualThreadApplication {
    public static void main(String[] args) {
        SpringApplication.run(VirtualThreadApplication.class, args);
    }
}

运行说明

  1. 启动应用程序后,访问 http://localhost:8080/virtual-thread

  2. 控制台会打印当前处理请求的线程信息,例如:

    Handling request in thread: VirtualThread[#1]/runnable@ForkJoinPool-1-worker-1
    
  3. 返回的响应为:

    Handled by virtual thread!
    

关键点说明

  1. Undertow 的线程池

    • 默认情况下,Undertow 使用传统的线程池来处理请求。
    • 在配置中,我们将 Undertow 的线程池替换为基于虚拟线程的线程池(Executors.newVirtualThreadPerTaskExecutor())。
  2. Spring WebFlux 的异步处理

    • WebFlux 使用 Mono 和 Flux 处理异步请求。
    • 在示例中,Mono.fromCallable 将阻塞操作封装为异步任务,并交由虚拟线程执行。
  3. 虚拟线程的优势

    • 每个请求由独立的虚拟线程处理,支持数百万级并发。
    • 阻塞操作(如 Thread.sleep)不会占用操作系统线程,资源利用率更高。

虚拟线程的好处

  1. 高并发性能

    • 虚拟线程支持数百万级并发,适合高并发 Web 服务。
  2. 简化编程模型

    • 支持阻塞式编程风格,开发者无需复杂的异步回调。
  3. 资源利用率更高

    • 阻塞操作不会占用操作系统线程,释放更多资源。
  4. 与 WebFlux 无缝集成

    • 虚拟线程可以直接替换传统线程池,兼容 WebFlux 的异步编程模型。