Java并发编程现在有什么新工具

wen java案例 2

本文目录导读:

Java并发编程现在有什么新工具

  1. 虚拟线程
  2. 结构化并发
  3. Scoped Values(作用域值)
  4. 传统工具的增强(JDK 8+)
  5. 总结:该用哪个?

Java 并发编程领域在 JDK 8 到 JDK 21 之间经历了一些非常重要的演进,但最核心、最具革命性的变化是 Project Loom 带来的虚拟线程,它在 JDK 21 正式转正为 GA(General Availability,通用可用性)版本。

以下是目前 Java 并发编程领域值得关注的新工具和机制:

虚拟线程

这是目前最重要、最应该掌握的新工具,它彻底改变了“高并发”的实现方式。

  • 是什么: 不再是操作系统级的线程(平台线程),而是由 JVM 管理的轻量级线程,你可以在一个平台线程上创建数以万计甚至百万计的虚拟线程。

  • 为什么重要: 传统 Thread 是重量级资源,主要靠“异步编程”和“回调”来压榨性能(如 CompletableFuture、WebFlux),虚拟线程的目标是让代码回归“同步 + 阻塞”的简单写法,同时获得极高的并发吞吐量。

  • 最佳实践: 每个业务请求直接创建一个新虚拟线程,不用线程池。

    // 直接创建启动
    Thread.startVirtualThread(() -> {
        // 这里可以放同步阻塞的IO操作(如数据库查询、RPC调用)
        System.out.println("Hello from virtual thread!");
    });
    // 使用Executors创建作用域(推荐)
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        executor.submit(() -> {
            Thread.sleep(1000); // 阻塞不会浪费OS线程
            return "done";
        });
    }

结构化并发

这是与虚拟线程配套出现的一种编程模型,目的是解决任务生命周期管理错误传播的问题。

  • 是什么: 它将多个并发任务绑定在同一个代码作用域内,当一个作用域结束时,其内部的所有并发子任务都必须完成或被取消,这避免了“任务泄露”和“悬空线程”。

  • 核心类: StructuredTaskScope

  • 作用:

    • 当你开启多个并发任务(同时查询用户信息和订单信息),如果任何一个任务失败,结构化并发可以自动取消其他未完成的任务,而不是让它们无意义地继续执行。
    • 替代传统 ExecutorService + CountDownLatch / Future 的复杂组合。
  • 示例:

    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        // 启动两个并发任务
        Future<String> user = scope.fork(() -> fetchUser());
        Future<Integer> order = scope.fork(() -> fetchOrderCount());
        // 等待所有任务完成,一旦失败立即取消剩余任务
        scope.join();
        scope.throwIfFailed();
        // 合并结果,主线程在此继续
        return user.resultNow() + " has " + order.resultNow() + " orders";
    } // 作用域结束,所有任务自动清理

Scoped Values(作用域值)

这是一个替代 ThreadLocal 的新工具,专为虚拟线程设计

  • 是什么: 允许在一个线程(及其子线程/虚拟线程)的作用域内,绑定一个不可变的值。

  • 为什么替代 ThreadLocal:

    • 虚拟线程数量巨大,使用 ThreadLocal 会导致内存开销剧增(因为每个虚拟线程都需要维护一份变量副本)。
    • 传统 ThreadLocal 的继承机制(InheritableThreadLocal)在虚拟线程下不工作(因为虚拟线程不是简单地继承自父线程)。
    • ScopedValue 的写入和读取有明确的作用域边界,不会逃脱到其他不相关的线程。
  • 核心用法: ScopedValue.where(key, value).run(callback)

    public static final ScopedValue<String> LOGGED_IN_USER = ScopedValue.newInstance();
    void processRequest() {
        // 在作用域内绑定值,作用域内执行的代码都能通过 get() 获取
        ScopedValue.where(LOGGED_IN_USER, "Alice")
                   .run(() -> {
                       // 任何在此作用域内启动的虚拟线程都能读到该值
                       Thread.startVirtualThread(() -> {
                           System.out.println("User: " + LOGGED_IN_USER.get()); // 输出 "Alice"
                       });
                   });
        // 离开作用域后,get() 会抛出异常或返回默认值
    }

传统工具的增强(JDK 8+)

除了上述“新”工具,一些已有工具在 JDK 8+ 也得到增强,但需要留意:

  • CompletableFuture: 仍然是处理非阻塞、无返回结果的异步任务的主流,它没有因为虚拟线程的出现而被淘汰,反而用于跨虚拟线程协调(将多个虚拟线程的结果组合成一个 CompletableFuture)。
  • 并发集合(ConcurrentHashMap, ConcurrentLinkedQueue): 针对虚拟线程的性能进行了优化(比如更细粒度的锁或无锁算法)。
  • ReentrantLock: 引入了条件变量可中断锁的支持,并且被虚拟线程的调度器(ForkJoinPool)特殊优化,当虚拟线程在 ReentrantLock 上阻塞时,会释放底层的平台线程去运行其他虚拟线程,这比 synchronized 关键字更高效(synchronized 在虚拟线程中会“钉住”平台线程)。

该用哪个?

场景 现代推荐方式 (JDK 21+) 传统方式 (JDK 8-11) 理由
高并发,IO密集型 虚拟线程 + StructuredTaskScope CompletableFuture + 线程池 代码简单,吞吐量高,无需改写成异步回调
需要线程本地数据(如用户上下文) ScopedValue ThreadLocal 更安全、更轻量,兼容虚拟线程
任务编排,超时控制 StructuredTaskScope CompletableFuture.orTimeout() + allOf() 结构化,错误传播清晰,自动取消
CPU密集型计算 还是用 平台线程(线程池) 平台线程池 + RecursiveTask 虚拟线程对于 CPU 密集型计算无明显优势(因为它们不会阻塞释放平台线程)
任务结果同步组合 CompletableFuture + 虚拟线程 CompletableFuture 虚拟线程执行异步操作,CompletionStage 仍用于组合结果

核心建议: 如果你正在使用 JDK 21 或更高版本,新项目应该优先使用虚拟线程 + 结构化并发ScopedValue 替代 ThreadLocalsynchronized 尽量改为 ReentrantLock(避免钉住平台线程)。

如果你还在用 JDK 11 或 8,则 CompletableFuture + ForkJoinPool 仍然是最主流、最有效的并发方案。

抱歉,评论功能暂时关闭!