• Java 17 新特性:sealed classes 与记录类的实际应用

    Java 17 作为长期支持(LTS)版本,带来了许多重要特性。其中,sealed classes(密封类)和记录类(records)对代码的可维护性、可读性和安全性有显著提升。本文将结合实例,深入探讨这两大特性的使用场景、实现原理及最佳实践。

    1. 记录类(Record)概述

    记录类是 JDK 14 引入的预览功能,在 JDK 16 成为正式特性。它们是一种轻量级的不可变数据载体,自动生成 equals、hashCode、toString 以及访问器方法,显著减少样板代码。

    1.1 基本语法

    public record User(String name, int age, String email) {}

    编译器会自动生成:

    • 私有 final 字段
    • 对应的访问器 name(), age(), email()
    • equals, hashCode, toString
    • 无参构造器和所有字段构造器

    1.2 适用场景

    • DTO(数据传输对象)
    • 事件对象(如在 CQRS/ES 中)
    • 简单配置对象

    1.3 记录类与传统类的区别

    方面 传统类 记录类
    代码量 较多 极少
    可变性 可变 不可变
    继承 继承自 Object 或自定义父类 自动继承自 java.lang.Record
    适用性 需要更多业务逻辑 纯数据承载

    2. 密封类(Sealed Class)概述

    密封类让你能够在编译时控制继承关系,限制哪些类可以扩展或实现某个类/接口,从而实现更安全、更易维护的设计。

    2.1 基本语法

    public sealed interface Shape permits Circle, Rectangle, Triangle {}
    
    final class Circle implements Shape { /* ... */ }
    final class Rectangle implements Shape { /* ... */ }
    non-sealed class Triangle implements Shape { /* ... */ }
    • sealed 修饰符后面必须使用 permits 关键字列出所有允许的子类型。
    • 子类型可以是 finalnon-sealed 或再次 sealed

    2.2 适用场景

    • 枚举风格的类层次(类似状态机、命令模式)
    • 必须在编译时确定可继承的子类
    • 设计者想要防止不受控制的继承导致的错误

    3. 结合实例:订单系统

    下面用一个订单系统的示例,演示如何同时使用记录类和密封类。

    // 订单类型
    public sealed class Order permits PhysicalOrder, DigitalOrder {}
    
    public final record PhysicalOrder(String id, String product, int quantity, String address)
            implements Order {}
    
    public final record DigitalOrder(String id, String product, int quantity, String downloadUrl)
            implements Order {}
    • Order 是密封类,只允许 PhysicalOrderDigitalOrder 继承。
    • 两个子类都是记录类,自动生成必要的构造器和方法。

    3.1 处理订单的策略

    public interface OrderProcessor {
        void process(Order order);
    }
    
    public class PhysicalOrderProcessor implements OrderProcessor {
        @Override
        public void process(Order order) {
            PhysicalOrder po = (PhysicalOrder) order; // 需要安全转换
            // 物流发货逻辑
        }
    }
    
    public class DigitalOrderProcessor implements OrderProcessor {
        @Override
        public void process(Order order) {
            DigitalOrder doOrder = (DigitalOrder) order;
            // 发送下载链接
        }
    }

    3.2 优点

    • 类型安全:编译器会检查 permits 列表,避免意外继承。
    • 简洁代码:记录类减少样板代码,易于阅读。
    • 不可变性:订单对象在创建后不再改变,避免并发问题。

    4. 性能与最佳实践

    4.1 性能考虑

    • 记录类使用 final 字段,JVM 能做更好优化。
    • 密封类在模式匹配(Java 21+)或 switch 时,编译器可进行更完整的类型检查,提升性能。

    4.2 代码组织

    • 把密封类放在单独包或模块,防止外部随意扩展。
    • 对记录类做细粒度分包,避免类膨胀。

    4.3 与 Lombok 的对比

    Lombok 通过注解生成样板代码,但会隐藏底层实现,导致 IDE 代码提示不完整。记录类直接用语言特性实现,IDE 能更好地理解代码结构。

    5. 结语

    Java 17 的 sealed classes 与 records 为 Java 开发者提供了更严谨、更简洁的代码结构。通过合理组合使用,可以构建出既安全又可维护的系统架构。建议从下一个项目起,尝试将这两大特性融入代码库,亲自感受它们带来的优势。祝编码愉快!

  • Java中实现线程安全的单例模式有哪些?

    在Java编程中,单例模式(Singleton Pattern)是一种常用的设计模式,旨在保证一个类只有一个实例,并为全局提供统一的访问点。实现线程安全的单例模式尤为重要,尤其是在多线程环境下。下面整理了几种主流的线程安全单例实现方法,并对每种方法的优缺点进行简要分析。

    1. 懒汉式 + synchronized

    public class Singleton {
        private static Singleton instance;
    
        private Singleton() { }
    
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    • 优点:实现简单,直接使用 synchronized 关键字,保证线程安全。
    • 缺点:每次访问 getInstance() 都需要同步,导致性能瓶颈。适用于实例创建成本高、使用频率低的场景。

    2. 双重检查锁(Double-Check Locking)

    public class Singleton {
        private static volatile Singleton instance;
    
        private Singleton() { }
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    • 优点:首次实例化后不再进行同步,性能优于单同步实现。
    • 缺点:实现复杂,需要 volatile 关键字保证可见性,否则可能产生线程安全问题。JDK 1.5 之后可以安全使用。

    3. 饿汉式(Eager Initialization)

    public class Singleton {
        private static final Singleton instance = new Singleton();
    
        private Singleton() { }
    
        public static Singleton getInstance() {
            return instance;
        }
    }
    • 优点:代码简洁,线程安全,无需同步。
    • 缺点:实例在类加载时就创建,可能导致资源浪费,尤其在实例初始化成本高、实际使用可能很少的情况下。

    4. 静态内部类(Initialization-on-demand Holder)

    public class Singleton {
        private Singleton() { }
    
        private static class Holder {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        public static Singleton getInstance() {
            return Holder.INSTANCE;
        }
    }
    • 优点:利用类加载机制,既实现了懒加载,又天然线程安全,性能最优。
    • 缺点:对静态内部类的概念和类加载机制需要一定了解。

    5. Enum实现单例

    public enum Singleton {
        INSTANCE;
    
        public void doSomething() {
            // 业务逻辑
        }
    }
    • 优点:Java的枚举类型天然实现了单例,且对反序列化、反射攻击都有防护,最为简洁安全。
    • 缺点:不适用于需要继承的场景,且与传统单例类相比可读性略逊。

    6. 使用 java.util.concurrent 包的 AtomicReference

    import java.util.concurrent.atomic.AtomicReference;
    
    public class Singleton {
        private static final AtomicReference
    <Singleton> INSTANCE = new AtomicReference<>();
    
        private Singleton() { }
    
        public static Singleton getInstance() {
            Singleton current = INSTANCE.get();
            if (current == null) {
                Singleton newInstance = new Singleton();
                if (INSTANCE.compareAndSet(null, newInstance)) {
                    return newInstance;
                } else {
                    return INSTANCE.get(); // 其它线程已完成创建
                }
            }
            return current;
        }
    }
    • 优点:无阻塞的线程安全实现,性能好,适合高并发场景。
    • 缺点:实现相对繁琐,代码可读性下降。

    选择指南

    场景 推荐实现 说明
    资源占用极小,且对线程安全无需求 饿汉式 简单直接,性能最好
    需要懒加载且代码简洁 静态内部类 兼顾懒加载与线程安全
    需要兼容老版本或想避开 volatile Enum 绝对安全,防止反射、序列化攻击
    需要更细粒度的并发控制 AtomicReference 适合高并发且不想使用锁
    需要对同步细粒度控制,且易读 双重检查锁 传统实现,需配合 volatile

    在实际项目中,推荐使用 静态内部类Enum 这两种实现方式,它们既安全又高效,且易于维护。只有在特殊需求(如需要手动销毁单例、支持多种实例化策略等)时,才考虑其他实现方案。

    小结

    Java提供了多种实现线程安全单例的手段,关键在于根据具体需求平衡性能、实现复杂度与可维护性。通过本文的对比分析,开发者可以快速定位最合适的实现方式,避免因单例实现不当导致的线程安全问题或性能瓶颈。

  • 如何在 Java 中使用 CompletableFuture 进行异步流水线处理?

    在现代 Java 开发中,异步编程已成为提升应用性能和响应性的关键技术之一。Java 8 引入的 CompletableFuture 为我们提供了一个强大的工具,用于构建可组合、非阻塞的异步流程。本文将从基础概念开始,逐步演示如何使用 CompletableFuture 搭建一个异步流水线,并分享几个常见的实战技巧。

    1. CompletableFuture 基础

    CompletableFuture 继承自 Future 接口,并且实现了 CompletionStage 接口,意味着它不仅可以像 Future 一样等待结果,还能在结果完成后继续执行后续操作。其核心优势在于:

    • 异步执行:不阻塞调用线程,结果通过回调或继续链式处理;
    • 组合能力:可以并行执行多个任务,并在全部完成后合并结果;
    • 异常处理:支持统一的异常捕获和恢复机制。

    2. 构建一个简单的异步流水线

    假设我们需要完成以下业务流程:

    1. 从数据库读取用户信息(耗时操作);
    2. 根据用户信息调用外部支付接口进行支付(耗时操作);
    3. 将支付结果写回数据库并发送通知。

    我们可以将每一步封装为 CompletableFuture,并使用 thenCompose 将它们串联起来。

    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class PaymentPipeline {
    
        private final ExecutorService dbExecutor = Executors.newFixedThreadPool(5);
        private final ExecutorService paymentExecutor = Executors.newFixedThreadPool(10);
    
        public CompletableFuture
    <Void> processPayment(String userId, double amount) {
            // 第一步:读取用户信息
            CompletableFuture
    <User> userFuture = CompletableFuture.supplyAsync(
                    () -> dbQueryUser(userId), dbExecutor);
    
            // 第二步:执行支付
            CompletableFuture
    <PaymentResult> paymentFuture = userFuture.thenCompose(
                    user -> CompletableFuture.supplyAsync(
                            () -> callPaymentApi(user, amount), paymentExecutor));
    
            // 第三步:写入结果并发送通知
            return paymentFuture.thenAcceptAsync(
                    result -> {
                        dbUpdatePaymentResult(result);
                        sendNotification(result);
                    }, dbExecutor);
        }
    
        // 以下为占位方法,实际业务中会有完整实现
        private User dbQueryUser(String userId) { /* ... */ }
        private PaymentResult callPaymentApi(User user, double amount) { /* ... */ }
        private void dbUpdatePaymentResult(PaymentResult result) { /* ... */ }
        private void sendNotification(PaymentResult result) { /* ... */ }
    }

    关键点说明

    • supplyAsync:在指定线程池中执行耗时任务,返回 `CompletableFuture `;
    • thenCompose:将前一步的结果作为参数传递给后续异步任务,保持链式调用;
    • thenAcceptAsync:在完成后执行副作用操作(如写入 DB、发送通知),同样指定线程池。

    3. 并行执行与组合

    在某些场景下,我们需要并行执行多个子任务,然后合并结果。例如,批量查询订单详情:

    List<CompletableFuture<Order>> futures = orderIds.stream()
            .map(id -> CompletableFuture.supplyAsync(() -> fetchOrder(id), dbExecutor))
            .collect(Collectors.toList());
    
    CompletableFuture
    <Void> allDone = CompletableFuture.allOf(
            futures.toArray(new CompletableFuture[0]));
    
    CompletableFuture<List<Order>> allOrders = allDone.thenApply(v ->
            futures.stream()
                   .map(CompletableFuture::join)
                   .collect(Collectors.toList()));
    • CompletableFuture.allOf:等待所有任务完成;
    • join:获取已完成任务的结果,若有异常会抛出 CompletionException

    4. 异常处理

    CompletableFuture 的异常处理方式与传统 Future 不同。我们可以在链中使用 exceptionallyhandlewhenComplete

    paymentFuture
        .exceptionally(ex -> {
            log.error("Payment failed", ex);
            return defaultPaymentResult(); // 兜底结果
        })
        .thenAcceptAsync(...);
    • exceptionally:仅处理异常,返回替代值;
    • handle:无论成功或失败都执行,能同时访问结果和异常;
    • whenComplete:不影响链中后续步骤,常用于日志记录。

    5. 性能调优建议

    1. 合理配置线程池:CPU 密集型任务使用 Executors.newWorkStealingPool(),I/O 密集型使用固定大小池;
    2. 避免线程切换:尽量在同一个线程池内完成相关操作,减少上下文切换;
    3. 使用 ForkJoinPool.commonPool():对于不需要高并发控制的轻量任务,可以直接使用全局共享池;
    4. 监控任务延迟:通过自定义 CompletableFuture 或使用 CompletableFuturethenApplyAsync 包装监控逻辑。

    6. 总结

    • CompletableFuture 让 Java 的异步编程变得更直观、可组合;
    • 通过 thenComposethenAcceptAsync 等方法可以轻松构建流水线式流程;
    • 并行组合、异常处理与性能调优是使用时需要关注的关键点。

    掌握这些技巧后,你就可以在自己的项目中用 CompletableFuture 替代繁琐的回调、线程池管理,让代码更简洁、易维护,同时充分利用多核 CPU 提升吞吐量。祝你编码愉快!

  • 如何在Java 8中使用CompletableFuture实现异步流水线?

    在Java 8中,CompletableFuture 提供了一种强大而灵活的方式来处理异步编程。通过它,你可以轻松地将多个异步任务组合成一个流水线(pipeline),每一步都在前一步完成后自动执行。下面通过一个完整的例子,演示如何构建一个包含三层处理的异步流水线,并介绍常用的组合方法、错误处理以及性能注意事项。


    1. 业务场景

    假设你在开发一个商品详情查询服务,业务流程如下:

    1. 查询商品基本信息(从 MySQL 读取)。
    2. 获取商品价格(调用第三方 REST API)。
    3. 生成推荐商品列表(使用机器学习模型)。

    这三步之间存在依赖关系:第二步需要第一步返回的商品 ID,第三步需要前两步的结果。传统的同步实现会导致阻塞,尤其是网络请求和数据库 IO。使用 CompletableFuture,可以让这些步骤并行或按需异步执行,提升整体吞吐量。


    2. 基础组件

    // ① 商品基本信息查询
    private CompletableFuture
    <Product> fetchProduct(String productId) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟 DB 查询
            try { Thread.sleep(100); } catch (InterruptedException ignored) {}
            return new Product(productId, "Demo Product");
        }, executor);
    }
    
    // ② 调用第三方价格服务
    private CompletableFuture
    <Double> fetchPrice(String productId) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟网络请求
            try { Thread.sleep(200); } catch (InterruptedException ignored) {}
            return 99.99;
        }, executor);
    }
    
    // ③ 机器学习推荐
    private CompletableFuture<List<Product>> recommendProducts(Product product, Double price) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟推理
            try { Thread.sleep(150); } catch (InterruptedException ignored) {}
            return Arrays.asList(
                    new Product(product.id + "-A", "Recommended A"),
                    new Product(product.id + "-B", "Recommended B"));
        }, executor);
    }

    注意executor 可以是自定义的 ThreadPoolExecutor,也可以直接使用 ForkJoinPool.commonPool()。为避免阻塞,建议使用固定线程数,避免 I/O 阻塞导致线程枯竭。


    3. 组合成流水线

    3.1 简单链式

    CompletableFuture<List<Product>> pipeline =
        fetchProduct(productId)
            .thenCombine(
                fetchPrice(productId),
                (product, price) -> new AbstractMap.SimpleEntry<>(product, price))
            .thenCompose(entry -> recommendProducts(entry.getKey(), entry.getValue()));
    • thenCombine:等待两个 CompletableFuture 均完成后,使用提供的 BiFunction 产生新结果。
    • thenCompose:把返回的 CompletableFuture 直接“展开”,形成真正的链式调用。

    3.2 并行化前两步

    如果你想在查询商品基本信息后立即并行发起价格请求(不需要商品基本信息才可发起价格请求),可以改写为:

    CompletableFuture
    <Product> productFuture = fetchProduct(productId);
    CompletableFuture
    <Double> priceFuture = fetchPrice(productId);
    
    CompletableFuture<List<Product>> pipeline =
        productFuture
            .thenCompose(product ->
                priceFuture
                    .thenCompose(price -> recommendProducts(product, price))
            );

    此时,productFuturepriceFuture 在同一线程池中并行执行,但两者仍需等待对方完成才能进入推荐阶段。

    3.3 统一错误处理

    pipeline
        .exceptionally(ex -> {
            // 统一处理异常,例如返回空列表
            System.err.println("Pipeline error: " + ex);
            return Collections.emptyList();
        })
        .thenAccept(recommended -> System.out.println("推荐结果: " + recommended));
    • exceptionally:在任何一步抛异常时捕获,并返回默认值。
    • thenAccept:最终消费结果。

    4. 性能小贴士

    场景 建议 说明
    CPU 密集型 使用 ForkJoinPool.commonPool() 或自定义 ForkJoinPool 充分利用多核 CPU
    IO 密集型 自定义 ThreadPoolExecutor,线程数可大于 CPU 核数 让线程在等待 I/O 时继续工作
    错误链 通过 handlewhenComplete 在链中处理异常 避免抛错导致整个链中断
    超时 completeOnTimeout / orTimeout 给每个步骤设定合理超时
    调试 join() + printStackTrace()CompletableFuture.allOf() 观察所有阶段状态

    5. 完整示例

    import java.util.*;
    import java.util.concurrent.*;
    import java.util.function.*;
    
    public class AsyncPipelineDemo {
        private static final ExecutorService executor = Executors.newFixedThreadPool(8);
    
        public static void main(String[] args) {
            String productId = "12345";
    
            CompletableFuture<List<Product>> pipeline =
                fetchProduct(productId)
                    .thenCombine(
                        fetchPrice(productId),
                        (product, price) -> new AbstractMap.SimpleEntry<>(product, price))
                    .thenCompose(entry -> recommendProducts(entry.getKey(), entry.getValue()))
                    .exceptionally(ex -> {
                        System.err.println("Error: " + ex.getMessage());
                        return Collections.emptyList();
                    });
    
            // 阻塞等待结果(仅作演示,实际业务中可异步处理)
            List
    <Product> recommendations = pipeline.join();
            System.out.println("最终推荐: " + recommendations);
    
            executor.shutdown();
        }
    
        private static CompletableFuture
    <Product> fetchProduct(String productId) {
            return CompletableFuture.supplyAsync(() -> {
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                return new Product(productId, "Demo Product");
            }, executor);
        }
    
        private static CompletableFuture
    <Double> fetchPrice(String productId) {
            return CompletableFuture.supplyAsync(() -> {
                try { Thread.sleep(200); } catch (InterruptedException ignored) {}
                return 99.99;
            }, executor);
        }
    
        private static CompletableFuture<List<Product>> recommendProducts(Product product, Double price) {
            return CompletableFuture.supplyAsync(() -> {
                try { Thread.sleep(150); } catch (InterruptedException ignored) {}
                return Arrays.asList(
                        new Product(product.id + "-A", "Recommended A"),
                        new Product(product.id + "-B", "Recommended B"));
            }, executor);
        }
    
        private static class Product {
            final String id;
            final String name;
            Product(String id, String name) { this.id = id; this.name = name; }
            @Override public String toString() { return "Product{id='" + id + "', name='" + name + "'}"; }
        }
    }

    运行后,你将看到:

    最终推荐: [Product{id='12345-A', name='Recommended A'}, Product{id='12345-B', name='Recommended B'}]

    6. 小结

    • CompletableFuture 让异步编程变得可组合、易维护。
    • 通过 thenCombine/thenCompose 可以构建复杂的流水线。
    • 结合 exceptionallywhenComplete 等方法,实现统一错误处理。
    • 合理配置线程池和超时策略,提升性能与可靠性。

    希望这篇文章能帮助你快速上手 Java 8 的异步流水线,并在实际项目中取得显著性能提升。祝编码愉快!

  • Java 中的 CompletableFuture 如何实现并发链式调用?

    CompletableFuture 是 Java 8 引入的强大异步编程工具,它在 java.util.concurrent 包中提供了一个完整的 API 用来处理异步任务。相比传统的 Future,CompletableFuture 允许我们在任务完成后执行后续操作,支持链式调用、并行组合、异常处理等高级功能。下面将通过一个具体的示例来演示如何利用 CompletableFuture 实现并发链式调用。

    1. 基础场景说明

    假设我们需要完成以下业务流程:

    1. 查询用户基本信息(耗时 300ms)
    2. 根据用户 ID 查询订单列表(耗时 400ms)
    3. 根据订单列表计算总金额(耗时 200ms)
    4. 返回最终结果(将用户信息、订单列表、总金额封装为 DTO)

    在传统的同步实现中,上面四步会串行执行,整体耗时约为 1.3 秒。利用 CompletableFuture,我们可以让“查询订单列表”和“计算总金额”并行执行,显著降低整体耗时。

    2. 代码实现

    import java.util.*;
    import java.util.concurrent.*;
    import java.util.stream.*;
    
    public class CompletableFutureDemo {
    
        // 模拟耗时查询用户基本信息
        private static CompletableFuture
    <User> fetchUser(String userId) {
            return CompletableFuture.supplyAsync(() -> {
                sleep(300); // 模拟网络延迟
                return new User(userId, "张三");
            });
        }
    
        // 模拟耗时查询订单列表
        private static CompletableFuture<List<Order>> fetchOrders(String userId) {
            return CompletableFuture.supplyAsync(() -> {
                sleep(400);
                return Arrays.asList(
                    new Order("A001", 99.99),
                    new Order("A002", 49.50),
                    new Order("A003", 149.75)
                );
            });
        }
    
        // 计算订单总金额
        private static CompletableFuture
    <Double> computeTotal(List<Order> orders) {
            return CompletableFuture.supplyAsync(() -> {
                sleep(200);
                return orders.stream()
                             .mapToDouble(Order::getAmount)
                             .sum();
            });
        }
    
        // 组合结果
        private static CompletableFuture
    <UserOrderSummary> buildSummary(User user, List<Order> orders, Double total) {
            return CompletableFuture.completedFuture(
                new UserOrderSummary(user, orders, total));
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            String userId = "U12345";
    
            // 第一步:查询用户信息
            CompletableFuture
    <User> userFuture = fetchUser(userId);
    
            // 第二步:查询订单列表(并行)
            CompletableFuture<List<Order>> ordersFuture = fetchOrders(userId);
    
            // 第三步:计算总金额(等待订单查询完成后再启动)
            CompletableFuture
    <Double> totalFuture = ordersFuture.thenCompose(orders -> computeTotal(orders));
    
            // 第四步:组合所有结果
            CompletableFuture
    <UserOrderSummary> summaryFuture = userFuture
                .thenCombine(ordersFuture, (user, orders) -> new AbstractMap.SimpleEntry<>(user, orders))
                .thenCombine(totalFuture, (entry, total) -> buildSummary(entry.getKey(), entry.getValue(), total))
                .thenCompose(Function.identity());
    
            // 读取最终结果
            UserOrderSummary summary = summaryFuture.get();
            System.out.println(summary);
        }
    
        // 简单工具方法
        private static void sleep(long ms) {
            try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
    
        // 数据模型
        static class User { String id; String name; User(String id, String name){this.id=id;this.name=name;} @Override public String toString(){return "User{id='"+id+"',name='"+name+"'}";}}
        static class Order { String id; double amount; Order(String id, double amount){this.id=id;this.amount=amount;} double getAmount(){return amount;} @Override public String toString(){return "Order{id='"+id+"',amount="+amount+"}";}}
        static class UserOrderSummary { User user; List
    <Order> orders; double total; UserOrderSummary(User u, List<Order> o, double t){user=u;orders=o;total=t;} @Override public String toString(){return "UserOrderSummary{user="+user+",orders="+orders+",total="+total+"}";}}
    }

    3. 关键点拆解

    步骤 关键 API 作用
    1 CompletableFuture.supplyAsync() 在 ForkJoinPool.commonPool() 中启动异步任务
    2 thenCompose() 先等待前一步完成,再执行后续异步操作(保持链式顺序)
    3 thenCombine() 并行等待两个 CompletableFuture 完成后合并结果
    4 thenCompose(Function.identity()) 当上一阶段返回的是 CompletableFuture 时,展开为真正的结果

    4. 性能收益

    在单线程同步实现中,四步共耗时约 1300ms。使用上述并发链式调用,fetchOrderscomputeTotal 可以并行执行(只需等待最长的一段 400ms),最终耗时约为 700ms 左右,提升近 50%。若业务场景更加复杂,例如多层嵌套调用、条件分支,CompletableFuture 的链式组合仍能保持代码可读性与可维护性。

    5. 异常处理

    CompletableFuture 提供 handle()exceptionally()whenComplete() 等方法用于统一捕获异常。示例:

    CompletableFuture
    <UserOrderSummary> safeFuture = summaryFuture
        .exceptionally(ex -> {
            System.err.println("出现错误:" + ex.getMessage());
            return new UserOrderSummary(new User("unknown", "error"), Collections.emptyList(), 0.0);
        });

    这样即使任意一步抛出异常,也能返回一个默认结果,保证业务流程不会因单个错误而中断。

    6. 小结

    • CompletableFuture 为 Java 8+ 提供了灵活且强大的异步编程模型。
    • 通过 链式调用thenCompose, thenCombine 等),可以轻松实现多步并行与串行组合。
    • 异常处理、结果回调均可通过专门的 API 实现,保持代码整洁。
    • 在实际项目中,只需将原同步业务拆分为若干异步方法,即可享受到性能提升与代码可维护性的双重好处。

    提示:在生产环境中,建议将 CompletableFuture 的执行线程池替换为自定义 Executor,以控制并发度并避免 ForkJoinPool.commonPool() 过载。

  • Java 8 中的默认方法:如何安全地在接口里实现逻辑?

    在 Java 8 之前,接口只能声明方法,而不能提供实现。Java 8 引入了“默认方法”(default methods),允许接口中定义具体实现,从而在不破坏向后兼容性的前提下向已有接口添加新功能。虽然默认方法带来了便利,但在使用时也需要注意潜在的陷阱,特别是在多重继承、性能和可维护性方面。下面我们系统梳理默认方法的正确使用方式,并给出常见问题的解决方案。

    一、默认方法的基本语法

    public interface Printable {
        void print(); // 抽象方法
    
        default void printHeader() {
            System.out.println("=== Header ===");
        }
    
        default void printFooter() {
            System.out.println("=== Footer ===");
        }
    }

    实现类可以直接继承这些默认实现,也可以根据需要覆盖。

    二、常见使用场景

    1. 向旧接口添加新功能
      通过在接口里添加默认实现,后续实现类可以自动获得新功能,而不必修改已有实现代码。

    2. 提供工具方法
      在接口中加入 staticdefault 方法,用来完成一些辅助功能,如数据校验、格式化等。

    3. 复合接口
      多个接口之间共享部分实现,可以把公共方法写成默认方法,减少代码重复。

    三、注意事项与陷阱

    1. 多重继承冲突

    interface A {
        default void foo() { System.out.println("A.foo"); }
    }
    interface B {
        default void foo() { System.out.println("B.foo"); }
    }
    class C implements A, B { }

    此时编译错误:C 必须重写 foo()。解决办法是:

    • 在 C 中显式实现 foo() 并决定使用 A 或 B 的实现:A.super.foo();B.super.foo();
    • 或者在 A/B 里让方法返回 default 形式的实现,供实现类根据需要调用。

    2. 性能影响

    默认方法实际上在编译时会被转换成抽象类的方法调用,并在运行时通过虚方法表调用。与直接在实现类中实现方法相比,开销微乎其微,但在极端性能敏感的循环里可能累积。建议:

    • 只在真正需要复用的逻辑中使用默认方法。
    • 避免在热点路径里调用大量默认方法。

    3. 与抽象方法同名

    如果接口里既有抽象方法也有同名默认方法,编译器会认为是冲突,要求实现类自行实现。避免使用同名,以免误解。

    4. 设计原则

    • 接口纯粹性:尽量保持接口只定义行为;若方法实现过于复杂,考虑将其放入工具类。
    • 单一职责:默认方法不宜承担过多职责,否则实现类维护成本高。
    • 可测试性:默认方法的测试可以像普通类一样进行,但若逻辑复杂,建议拆分成独立方法,方便单元测试。

    四、实战案例:Stream 风格的集合操作

    Java 8 的 Stream 接口使用了大量默认方法来实现链式操作。我们可以借鉴其模式,自己定义一个 ListOps 接口:

    public interface ListOps
    <T> extends Iterable<T> {
        // 抽象方法
        List
    <T> asList();
    
        // 默认方法:过滤
        default ListOps
    <T> filter(Predicate<? super T> predicate) {
            List
    <T> result = new ArrayList<>();
            for (T item : asList()) {
                if (predicate.test(item)) result.add(item);
            }
            return new SimpleListOps<>(result);
        }
    
        // 默认方法:映射
        default 
    <R> ListOps<R> map(Function<? super T, ? extends R> mapper) {
            List
    <R> result = new ArrayList<>();
            for (T item : asList()) {
                result.add(mapper.apply(item));
            }
            return new SimpleListOps<>(result);
        }
    }

    实现类 SimpleListOps 只需实现 asList(),其余逻辑即为默认实现。这样即可在任何实现类上链式调用 filtermap,极大提升代码可读性。

    五、最佳实践总结

    1. 只在接口里放置真正通用、简单的实现,复杂逻辑请放入工具类或抽象类。
    2. 避免多重继承冲突:明确接口层级关系,必要时在实现类中显式选择实现。
    3. 保持默认方法的文档化:在接口文档中标明默认实现的行为,避免实现类误用。
    4. 关注性能:在热点代码里避免频繁调用默认方法,必要时将其改写为内联实现。
    5. 测试:为默认方法编写单元测试,保证其在所有实现类中的一致性。

    默认方法是 Java 8 之后接口设计的一大进步,合理使用能显著提升代码的可维护性和可扩展性。只要遵循上述原则,你就能在项目中安全、高效地运用默认方法,为接口设计注入更多活力。

  • Java 17:模块化、记录类与更高效的文本处理

    在 Java 17 版中,官方对语言和运行时环境做了多项重要改进,为开发者提供了更简洁、可维护的编程方式。本文将重点探讨三大核心特性:模块化系统(Java Platform Module System, JPMS)的演进、记录类(record)的引入,以及使用新的文本块(Text Blocks)和流式 API(Streams)实现高效文本处理的实践。


    一、模块化系统(JPMS)的进一步完善

    自 Java 9 开始,JPMS 为大型应用提供了强大的模块划分能力。Java 17 在此基础上增加了以下改进:

    1. 自动模块化改进:对 JDK 自带的自动模块化 jar 进行更细粒度的包限制,减少无用导出,提升安全性。
    2. 模块路径的可读性增强:引入 --module-path 参数的简化形式,支持 glob 匹配和分隔符 : 以外的 ;(Windows)。
    3. 更灵活的 requires transitive:可以在同一模块内部使用 requires transitive 来隐藏内部实现,减少对外暴露。

    这些改进使得在构建微服务、插件化框架时,模块边界更清晰,依赖关系更可视化,极大提升维护成本。


    二、记录类(record)的实战案例

    记录类是 Java 16 引入的语法糖,用于简化数据载体(DTO)的编写。与普通类不同,record 自动生成了 equalshashCodetoString、以及访问器方法。以下是一个典型的使用示例:

    public record UserDTO(
            int id,
            String name,
            String email,
            LocalDateTime registeredAt
    ) {}

    1. 不可变性

    Record 的字段默认是 final,确保不可变性。这在并发编程中可以避免多线程同步问题。

    2. 组合式构造函数

    public record UserDTO(int id, String name, String email, LocalDateTime registeredAt) {
        public UserDTO(String name, String email) {
            this(0, name, email, LocalDateTime.now());
        }
    }

    使用这种构造器可以在创建对象时提供默认值。

    3. 记录类与 JSON 序列化

    大多数 JSON 序列化框架(如 Jackson、Gson)对 record 支持良好。只需在 pom 里添加对应依赖即可:

    
    <dependency>
    
    <groupId>com.fasterxml.jackson.module</groupId>
    
    <artifactId>jackson-module-parameter-names</artifactId>
    
    <version>2.15.0</version>
    </dependency>

    然后即可通过 ObjectMapper 直接序列化/反序列化 record。


    三、文本块与流式 API 的结合

    Java 13 开始引入文本块(Text Blocks),允许使用多行字符串字面量,减少转义字符。配合 StreamCollectors,可以轻松实现高效文本处理。下面给出一个完整的示例:解析配置文件中的多行注释并提取键值对。

    String config = """
            # 数据库配置
            host=localhost
            port=5432
            user=admin
            password=secret
    
            # 其他配置
            timeout=30
            """;
    
    Map<String, String> map = Arrays.stream(config.split("\\R"))
            .filter(line -> !line.trim().startsWith("#") && !line.isBlank())
            .map(line -> line.split("=", 2))
            .collect(Collectors.toMap(a -> a[0].trim(), a -> a[1].trim()));
    
    System.out.println(map);

    优点

    • 可读性高:文本块保持原始格式,避免多余的转义符号。
    • 流式操作简洁filtermapcollect 三步实现,代码更短、易维护。
    • 性能String.split("\\R") 在 JDK 17 中已做优化,分割多行文本的性能表现接近 BufferedReader 读取文件。

    四、总结

    Java 17 为开发者提供了更完善的模块化支持、更简洁的数据载体记录类,以及强大的文本处理能力。通过结合这些新特性,程序员可以:

    • 在大型项目中更好地控制依赖关系,提升代码安全性与可维护性;
    • 以更少的代码实现不可变数据对象,减少错误率;
    • 使用文本块与流式 API 高效处理文本文件,保持代码简洁。

    在后续版本中,Java 继续关注性能、易用性与安全性。掌握并善用上述特性,将为你在 Java 开发道路上奠定坚实基础。

  • Java 并发编程中的可见性问题与 JMM 详解

    在多线程环境下,变量的可见性是最常见且难以调试的问题之一。Java 内存模型(Java Memory Model, JMM)规定了线程之间如何共享数据,如何保证可见性,以及何时可以认为一个操作“完成”。理解 JMM 对编写安全、高效的并发代码至关重要。以下内容将系统梳理可见性、happens‑before 关系、volatile 与锁的实现机制,并给出实战中的常见陷阱与最佳实践。

    1. 可见性基本概念

    • 共享变量:被多个线程访问的变量。
    • 可见性:当一个线程修改了共享变量的值,其他线程是否能及时看到该修改。
    • 缓存:每个线程都有自己的工作内存(CPU 缓存),JMM 允许线程将共享变量从主内存拷贝到工作内存,或者把工作内存中的修改写回主内存。

    若没有任何同步手段,线程 A 修改了共享变量后,线程 B 可能一直使用缓存中的旧值,导致程序产生不可预期的行为。

    2. JMM 的 Happens‑Before 规则

    Happens‑before(HB)是 JMM 的核心概念,描述两个操作之间的“顺序”关系。若 A HB B,则 A 的结果对 B 可见。HB 的三种基本来源:

    1. 程序顺序规则:在同一线程中,代码的先后顺序即为 HB。
    2. 监视器锁规则:线程释放锁后,随后获取同一把锁的线程,释放前的所有写操作对获取后的线程可见。
    3. volatile 规则:写 volatile 变量后,后续对 volatile 的读操作对前一次写可见。

    只有满足 HB,才保证可见性。若 HB 失效,可出现“脏读”或“不可见写”。

    3. volatile 关键字的实现细节

    volatile 在 JMM 中的含义是:

    • 禁止指令重排:保证写入 volatile 前的所有写操作先完成。
    • 强制刷新/读取:写 volatile 时会把值从工作内存刷新到主内存;读 volatile 时会从主内存重新读取。

    但 volatile 只保证单个变量的原子读写,对于复合操作(如 x++)仍需外部同步。

    示例

    volatile boolean stop = false;
    public void run() {
        while (!stop) {
            // 业务逻辑
        }
    }
    public void cancel() {
        stop = true;   // 线程 A 设定停止
    }

    这里 stop 的写入会立即对所有线程可见,run 方法中的 while 循环会及时结束。

    4. 锁(synchronized/Lock)与可见性

    锁实现可见性的关键在于 monitorentermonitorexit 指令。

    • monitorexit 时,线程会把工作内存中的所有被锁保护的共享变量写回主内存。
    • monitorenter 时,线程会把主内存中的变量刷新到工作内存。

    因此,解锁-加锁形成的 HB 关系确保可见性。

    注意:使用 synchronized 时,锁的粒度和锁的对象决定了 HB 的范围。若锁对象不同,则 HB 失效,导致不可见。

    5. 复合操作的可见性与线程安全

    下面展示一个典型的“计数器”并发更新问题:

    public class Counter {
        private int value = 0;
        public void inc() { value++; }
        public int get() { return value; }
    }

    若多线程同时调用 inc(),即使每个操作都对单个线程可见,整体更新仍可能出现丢失。原因:value++ 不是原子操作,内部拆成读、加、写三步,缺少 HB 保护。

    解决方案

    1. synchronized
    public synchronized void inc() { value++; }
    1. AtomicInteger(JDK 并发包提供):
    AtomicInteger counter = new AtomicInteger(0);
    public void inc() { counter.incrementAndGet(); }
    1. volatile + CAS
    volatile int value = 0;
    public void inc() {
        int prev, next;
        do {
            prev = value;
            next = prev + 1;
        } while (!compareAndSwap(prev, next));
    }

    6. 常见陷阱与最佳实践

    陷阱 说明 对策
    只在字段上加 volatile,未同步复合操作 可能出现不可见写 对所有涉及的共享字段使用 volatile 或同步块
    锁对象错误(如 synchronized(this)synchronized(other) HB 失效 保证所有相关操作使用同一锁对象
    只使用 volatile 而忽略内存屏障 某些 JIT 可能重排 volatile 本身提供重排屏障,复合操作请使用锁或 CAS
    不考虑初始化时的可见性 线程在对象构造后立即使用,字段可能不可见 使用 volatile 或在构造后立即发布对象(如通过 final 字段)
    误用 Thread.stop()Thread.suspend() 破坏可见性与 HB 绝不使用已废弃 API,改用中断或共享标志

    7. 结语

    JMM 为 Java 并发提供了严格的理论基础,但实际编程中要通过合适的同步机制(volatile、synchronized、Lock、Atomic)来实现可见性。理解 HB 规则、缓存一致性、以及复合操作的原子性,才能写出既安全又高效的多线程程序。建议在编写并发代码前先构建可见性模型,避免后期难以排查的“隐形 bug”。


    小结:可见性是多线程安全的核心。通过 JMM 的 HB 规则和正确使用 volatile/锁/原子类,可以保证共享变量在各线程间及时同步,避免不可预期的行为。务必在设计时把握同步粒度与锁对象,避免不必要的性能损耗。

  • Java反射机制:从基础到高级实践

    Java中的反射机制是指在运行时动态地获取类的信息,并对其进行操作的一种技术。它不仅为框架开发提供了强大的支持,也为动态代理、ORM、依赖注入等技术奠定了基础。本文将从反射的基本概念开始,逐步深入到高级使用场景,并给出实践代码示例,帮助读者快速掌握并应用反射技术。


    1. 反射基础概述

    1.1 什么是反射

    在Java中,反射是一种在运行时获取类、方法、字段等信息,并对其进行操作的能力。通过反射,你可以:

    • 动态获取类的完整信息(字段、方法、构造器、注解等)
    • 动态实例化对象
    • 动态调用方法
    • 动态访问字段值

    1.2 反射的核心类

    • java.lang.Class: 表示类或接口的对象
    • java.lang.reflect.Method: 表示方法
    • java.lang.reflect.Field: 表示字段
    • java.lang.reflect.Constructor: 表示构造器
    • java.lang.reflect.Modifier: 提供访问修饰符相关工具

    1.3 获取Class对象的方式

    // 1. 通过类名获取
    Class<?> clazz = MyClass.class;
    
    // 2. 通过对象获取
    MyClass obj = new MyClass();
    Class<?> clazz = obj.getClass();
    
    // 3. 通过Class.forName()加载
    Class<?> clazz = Class.forName("com.example.MyClass");

    2. 反射的常用操作

    2.1 动态实例化对象

    Constructor
    <MyClass> ctor = MyClass.class.getConstructor(String.class, int.class);
    MyClass instance = ctor.newInstance("demo", 10);

    2.2 动态调用方法

    Method method = MyClass.class.getMethod("setName", String.class);
    method.invoke(instance, "newName");

    2.3 动态访问字段

    Field field = MyClass.class.getDeclaredField("age");
    field.setAccessible(true); // 取消访问限制
    int age = field.getInt(instance);

    2.4 访问注解

    Annotation[] annotations = MyClass.class.getAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation);
    }

    3. 反射的性能与安全性

    3.1 性能问题

    • 耗时:每次反射调用都会触发安全检查、访问权限校验,导致性能下降。
    • 缓存:常用的ClassMethodField对象可缓存,以减少反射次数。

    3.2 安全问题

    • 访问限制setAccessible(true) 可能绕过 Java 的访问控制,导致安全风险。
    • 安全管理器:在安全管理器(SecurityManager)下使用反射会受到限制,需要相应权限。

    4. 反射在实际框架中的应用

    框架 作用 关键反射技术
    Spring Bean 生命周期管理 BeanFactory通过反射实例化 Bean、调用 Setter
    MyBatis 动态 SQL 映射 通过注解和 XML 配置反射获取方法签名
    Hibernate ORM 映射 通过反射读取实体字段并生成 SQL
    JUnit 单元测试 通过 @Test 注解反射执行测试方法

    5. 进阶:动态代理与增强

    5.1 JDK 动态代理

    interface Service { void execute(); }
    class ServiceImpl implements Service { public void execute() { System.out.println("原始执行"); } }
    
    Service proxy = (Service) Proxy.newProxyInstance(
            ServiceImpl.class.getClassLoader(),
            new Class[]{Service.class},
            (proxyObj, method, args) -> {
                System.out.println("Before method");
                Object result = method.invoke(new ServiceImpl(), args);
                System.out.println("After method");
                return result;
            });
    
    proxy.execute();

    5.2 CGLIB 动态代理

    • 通过继承原类实现增强
    • 常用于 Spring AOP 的类级代理

    6. 实战案例:自定义注解与属性注入

    6.1 定义注解

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Inject {
        String value() default "";
    }

    6.2 注解处理器

    public class Injector {
        public static void inject(Object target) throws IllegalAccessException {
            Class<?> clazz = target.getClass();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Inject.class)) {
                    field.setAccessible(true);
                    // 简单示例:按字段类型实例化
                    Object instance = field.getType().newInstance();
                    field.set(target, instance);
                }
            }
        }
    }

    6.3 使用示例

    class Service {
        @Inject
        private Repository repo;
    
        public void run() {
            repo.query();
        }
    }
    Service s = new Service();
    Injector.inject(s);
    s.run();

    7. 小结

    • 反射是 Java 的强大元编程能力,能在运行时动态获取并操作类信息。
    • 正确使用反射可极大提升框架灵活性,但需注意性能和安全风险。
    • 在实际开发中,反射多与注解、动态代理结合,构建 IoC、AOP、ORM 等核心功能。

    掌握了上述概念与代码实践后,你就能在自己的项目中自由运用反射,为代码注入更多灵活性与扩展性。祝你编码愉快!

  • 如何在Java中使用CompletableFuture实现异步编程?

    在Java 8及以后版本中,CompletableFuture提供了一套强大且灵活的机制来编写异步、非阻塞代码。它不仅能简化多线程开发,还能让代码保持可读性和可维护性。下面从基础概念、常用方法、并行组合以及最佳实践四个方面,系统介绍如何利用CompletableFuture实现高效的异步编程。

    1. 基础概念

    `CompletableFuture

    `是实现了`Future`和`CompletionStage`接口的类。其核心思想是: 1. **任务提交**:将耗时操作放入线程池执行。 2. **结果回调**:任务完成后立即触发后续操作,而无需显式等待。 3. **链式组合**:可以将多个异步任务串联、并行或条件地组合成复杂工作流。 ### 1.1 创建实例 “`java // 1. 创建一个立即完成的Future CompletableFuture cf1 = CompletableFuture.completedFuture(10); // 2. 提交任务到默认ForkJoinPool CompletableFuture cf2 = CompletableFuture.supplyAsync(() -> { // 模拟耗时操作 try { Thread.sleep(500); } catch (InterruptedException e) {} return “Hello”; }); // 3. 自定义Executor Executor executor = Executors.newFixedThreadPool(4); CompletableFuture cf3 = CompletableFuture.supplyAsync(() -> { // 计算过程 return Math.PI; }, executor); “` ## 2. 常用方法 | 方法 | 说明 | 示例 | |——|——|——| | `thenApply(Function)` | 任务完成后同步转换结果 | `cf2.thenApply(s -> s + ” World”)` | | `thenAccept(Consumer )` | 任务完成后执行消费(无返回值) | `cf2.thenAccept(System.out::println)` | | `thenRun(Runnable)` | 任务完成后执行无参无返回值任务 | `cf2.thenRun(() -> log(“完成”))` | | `thenCompose(Function>)` | 任务完成后根据结果提交新的异步任务 | `cf2.thenCompose(s -> callAnotherService(s))` | | `exceptionally(Function)` | 捕获异常并提供默认值 | `cf2.exceptionally(ex -> “默认”)` | | `allOf(CompletableFuture>… futures)` | 等待所有任务完成 | `CompletableFuture.allOf(cf1, cf2, cf3)` | | `anyOf(CompletableFuture>… futures)` | 任意一个完成即可 | `CompletableFuture.anyOf(cf1, cf2, cf3)` | ## 3. 并行组合实例 假设我们需要从三台服务器异步获取数据,然后合并结果。使用`CompletableFuture.allOf`可以实现: “`java CompletableFuture fetchA = CompletableFuture.supplyAsync(() -> fetchFromServer(“A”)); CompletableFuture fetchB = CompletableFuture.supplyAsync(() -> fetchFromServer(“B”)); CompletableFuture fetchC = CompletableFuture.supplyAsync(() -> fetchFromServer(“C”)); CompletableFuture all = CompletableFuture.allOf(fetchA, fetchB, fetchC); String combinedResult = all.thenApply(v -> { try { return fetchA.join() + fetchB.join() + fetchC.join(); } catch (CompletionException e) { // 处理单个任务异常 throw e; } }).get(); // 或者异步返回 “` > **注意**:`join()`在捕获异常时会包装成`CompletionException`,需要在外层捕获。 ### 3.1 并行流+CompletableFuture “`java List nums = Arrays.asList(1,2,3,4,5,6,7,8,9,10); List> futures = nums.stream() .map(n -> CompletableFuture.supplyAsync(() -> heavyComputation(n))) .collect(Collectors.toList()); CompletableFuture allDone = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); int sum = allDone.thenApply(v -> futures.stream().mapToInt(f -> f.join()).sum() ).join(); “` ## 4. 处理异常与超时 ### 4.1 捕获异常 “`java CompletableFuture safeFuture = originalFuture .exceptionally(ex -> { log(“异常: ” + ex.getMessage()); return “fallback”; }); “` ### 4.2 超时 “`java CompletableFuture timedFuture = originalFuture.orTimeout(2, TimeUnit.SECONDS) .exceptionally(ex -> { if (ex instanceof TimeoutException) { return “超时返回”; } throw new CompletionException(ex); }); “` > `orTimeout`和`completeOnTimeout`是Java 9+新增方法,前者在超时后抛出异常,后者在超时后直接返回默认值。 ## 5. 与传统Future对比 | 特点 | `Future` | `CompletableFuture` | |——|———-|———————| | 结果获取 | `get()` 阻塞 | 直接异步回调 | | 异步组合 | 不支持 | 支持链式、并行 | | 错误处理 | 通过异常捕获 | `exceptionally`等专门API | | 线程池 | 需要手动管理 | 由`CompletableFuture`内置或外部提供 | ## 6. 真实案例:微服务调用链 “`java public CompletableFuture fetchUserInfo(String userId) { // 1. 取用户基本信息 CompletableFuture userFuture = httpClient.getAsync(“/user/” + userId, User.class); // 2. 取用户订单 CompletableFuture> ordersFuture = httpClient.getAsync(“/orders?userId=” + userId, List.class); // 3. 合并结果 return userFuture.thenCombine(ordersFuture, (user, orders) -> { UserInfo info = new UserInfo(); info.setUser(user); info.setOrders(orders); return info; }); } “` 在上述示例中,`thenCombine`实现了两个并发请求的同步合并,整个调用链保持非阻塞且易读。 ## 7. 最佳实践 1. **不要滥用全局Executor**:为每类任务定义合适的线程池,避免资源竞争。 2. **避免`join()`/`get()`**:除非需要同步,否则应使用回调或`thenCompose`完成链式处理。 3. **异常要统一处理**:利用`exceptionally`或`handle`集中管理错误,避免遗漏。 4. **使用`thenCompose`实现链式异步**:而不是嵌套回调。 5. **监控和度量**:结合`CompletableFuture`的`whenComplete`或`handle`,记录任务耗时、成功/失败率。 ## 8. 小结 `CompletableFuture`是Java 8引入的异步编程利器,它通过函数式接口和强大的组合API,使得复杂的并发逻辑变得直观且易维护。掌握其核心方法和组合技巧后,你可以在微服务、IO密集型或计算密集型场景中快速实现高性能、可伸缩的异步业务。祝你编码愉快!