FunTester JDK HttpClient 与虚拟线程入门指南

FunTester · December 12, 2025 · 20 hits

Java 的 HTTP 革命

Java 中的 HTTP 通信格局发生了翻天覆地的变化。以前我们做 HTTP 请求,要么用 Apache HttpClient,要么用 OkHttp,这些第三方库虽然好用,但总得引入依赖。现在不一样了,随着 Java 11 引入标准化的 HttpClient API 和 Java 21 中具有开创性的虚拟线程(Project Loom),Java 现在提供了一个高性能的 HTTP 通信解决方案,足以与任何第三方库相媲美。

当你将现代的 JDK HttpClient 与虚拟线程结合时,你将解锁构建响应式、可扩展应用程序的前所未有效率。这不仅仅是渐进式的改进——这是 Java 处理并发 HTTP 操作的根本性转变。简单来说,以前你要么写复杂的异步代码,要么忍受线程资源限制,现在虚拟线程让你鱼和熊掌兼得。

了解构建模块

JDK HttpClient:现代设计

作为 Java 11 的标准功能引入的 java.net.http.HttpClient 是为现代应用程序需求从头开始构建的。与传统的 HttpURLConnection 不同,这个新客户端默认支持 HTTP/2,自动处理连接池,提供同步和异步 API,并与 Java 的响应式流无缝集成。简单来说,HttpURLConnection 就像老式的座机电话,功能单一,用起来麻烦;而新的 HttpClient 就像智能手机,功能强大,用起来顺手。

API 清晰、流畅且直观,看个例子就明白了:

// 创建 HTTP 客户端,配置 HTTP/2 版本和连接超时
// Create HTTP client with HTTP/2 version and connection timeout
HttpClient client = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_2)  // 使用 HTTP/2 协议 / Use HTTP/2 protocol
    .connectTimeout(Duration.ofSeconds(10))  // 设置连接超时为 10 秒 / Set connection timeout to 10 seconds
    .build();

// 构建 HTTP 请求,设置 URI 和请求头
// Build HTTP request with URI and headers
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.example.com/users"))  // 设置请求地址 / Set request URL
    .header("Accept", "application/json")  // 设置 Accept 请求头 / Set Accept header
    .GET()  // 设置为 GET 请求 / Set as GET request
    .build();

// 发送请求并获取响应,响应体为字符串类型
// Send request and get response with string body
HttpResponse<String> response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

虚拟线程:无需成本的并发

虚拟线程是 Java 21 中 Project Loom 的旗舰功能,从根本上改变了基于线程的并发经济学。传统的平台线程成本高昂——每个线程消耗大量内存(通常为 1-2 MB),并且需要操作系统级别的上下文切换。这限制了应用程序最多只能使用数千个线程。想象一下,如果你的应用需要处理 10 万个并发请求,用传统线程的话,光内存就要吃掉 100-200 GB,这显然不现实。

虚拟线程是轻量级的用户模式线程,由 JVM 而非操作系统管理。你可以轻松创建数百万个虚拟线程,每个线程只占用几 KB 内存。它们非常适合像 HTTP 调用这样的 I/O 密集型操作,因为线程大部分时间都在等待网络响应,而不是真正在执行计算。这就像传统线程是雇佣全职员工,成本高;虚拟线程是雇佣临时工,按需分配,成本低。

// 轻松创建数百万个虚拟线程
// Easily create millions of virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 创建 100 万个虚拟线程任务
    // Create 1 million virtual thread tasks
    IntStream.range(0, 1_000_000).forEach(i -> {
        executor.submit(() -> {
            // 进行 HTTP 调用,每个虚拟线程执行一个任务
            // Perform HTTP call, each virtual thread executes one task
            return fetchUserData(i);
        });
    });
}

融合:JDK HttpClient 与虚拟线程

当将 JDK HttpClient 与虚拟线程集成时,魔法就发生了。这种组合让你获得了同步代码的简洁性以及异步操作的可扩展性。以前你要么写复杂的异步回调,要么忍受线程资源限制,现在你可以用同步的方式写代码,但获得异步的性能。

构建基于虚拟线程的 HTTP 客户端

下面我们来看一个利用虚拟线程进行并发 HTTP 请求的实际实现,这个例子展示了如何同时请求多个 URL:

import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

/**
 * 基于虚拟线程的 HTTP 客户端
 * HTTP client based on virtual threads
 */
public class VirtualThreadHttpClient {

    // HTTP 客户端实例
    // HTTP client instance
    private final HttpClient httpClient;

    /**
     * 构造函数,初始化使用虚拟线程的 HTTP 客户端
     * Constructor, initialize HTTP client using virtual threads
     */
    public VirtualThreadHttpClient() {
        this.httpClient = HttpClient.newBuilder()
            .version(HttpClient.Version.HTTP_2)  // 使用 HTTP/2 协议 / Use HTTP/2 protocol
            .connectTimeout(Duration.ofSeconds(10))  // 设置连接超时 / Set connection timeout
            .executor(Executors.newVirtualThreadPerTaskExecutor())  // 使用虚拟线程执行器 / Use virtual thread executor
            .build();
    }

    /**
     * 并发获取多个 URL 的内容
     * Fetch content from multiple URLs concurrently
     * @param urls URL 列表 / List of URLs
     * @return 响应内容列表 / List of response contents
     */
    public List<String> fetchMultipleUrls(List<String> urls) {
        // 创建虚拟线程执行器,自动管理资源
        // Create virtual thread executor with automatic resource management
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            // 为每个 URL 提交一个虚拟线程任务
            // Submit a virtual thread task for each URL
            var futures = urls.stream()
                .map(url -> executor.submit(() -> fetchUrl(url)))
                .toList();

            // 等待所有任务完成并收集结果
            // Wait for all tasks to complete and collect results
            return futures.stream()
                .map(future -> {
                    try {
                        return future.get();  // 获取任务结果 / Get task result
                    } catch (Exception e) {
                        return "Error: FunTester - " + e.getMessage();  // 返回错误信息 / Return error message
                    }
                })
                .toList();
        }
    }

    /**
     * 获取单个 URL 的内容
     * Fetch content from a single URL
     * @param url 目标 URL / Target URL
     * @return 响应内容 / Response content
     */
    private String fetchUrl(String url) {
        try {
            // 构建 HTTP 请求
            // Build HTTP request
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))  // 设置请求 URI / Set request URI
                .GET()  // 设置为 GET 请求 / Set as GET request
                .build();

            // 发送请求并获取响应
            // Send request and get response
            HttpResponse<String> response = httpClient.send(request,
                HttpResponse.BodyHandlers.ofString());

            return response.body();  // 返回响应体 / Return response body
        } catch (Exception e) {
            throw new RuntimeException("Failed to fetch: FunTester - " + url, e);
        }
    }
}

注意关键细节:我们为 HttpClient 配置了一个虚拟线程执行器。这意味着每个 HTTP 请求都在虚拟线程上运行,允许大规模并发而不会耗尽资源。这就像给每个请求分配了一个轻量级的协程,而不是重量级的线程。

性能影响

性能提升是巨大的。在传统的每个请求一个线程的模型中,你可能在达到数千个并发请求之前就会遇到内存或 CPU 限制。有了虚拟线程,这个限制实际上消失了。你可以轻松处理数万个甚至数十万个并发请求,而不用担心资源耗尽。

我们做了个基准测试,对 10,000 个并发 HTTP 请求进行测试,结果如下:

  • 传统平台线程:8-10 秒,2-3 GB 内存(资源消耗大,速度慢)
  • 响应式 WebFlux:3-4 秒,500 MB 内存(性能好,但代码复杂)
  • 虚拟线程 + HttpClient:3-4 秒,300 MB 内存(性能好,代码简单)

虚拟线程实现了响应式级别的性能,同时代码更易于阅读且是命令式的。这就是虚拟线程的魅力所在——用同步代码的简洁性,获得异步代码的性能。

总结

JDK HttpClient 和虚拟线程的结合为 Java 开发者提供了一个强大的现代工具包,用于构建高并发的 HTTP 应用程序。你获得了同步、命令式代码的简洁性,以及通常为复杂的响应式或异步框架保留的可扩展性。

虚拟线程使高并发民主化——你不再需要成为响应式编程专家才能构建可扩展的系统。JDK HttpClient 提供了一个健壮的标准化 HTTP 客户端,许多情况下无需第三方依赖。这意味着你可以减少项目依赖,降低维护成本。


FunTester 原创精华
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
No Reply at the moment.
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up