Java并发编程——提前聊一聊CompletableFuture和相关业务场景

CompletableFuture 的异步机制详解

CompletableFuture 是 Java 8 引入的并发编程工具(位于 java.util.concurrent 包),它是 Future 接口的扩展实现,主要用于处理异步任务。它引入了"完成阶段"(Completion Stage)概念,支持链式编程、非阻塞执行和异常处理,使异步代码更简洁和可读。下面我从核心机制、执行流程、关键方法和示例逐步解释。

1. 核心概念:异步执行与完成阶段
  • 异步机制基础CompletableFuture 通过线程池(如默认的 ForkJoinPool.commonPool())将任务从主线程"卸载"到后台线程执行,避免阻塞主线程。这基于 Java 的 Executor 服务模型:
    • 当你调用 supplyAsyncrunAsync 时,任务被提交到线程池。
    • 任务完成后,CompletableFuture 对象会"完成"(complete),持有结果或异常。
    • 它支持回调式 处理:无需轮询 Future.get()(阻塞),而是用链式方法注册后续操作。
  • 完成阶段(Completion Stage) :每个 CompletableFuture 代表一个"阶段"。一个阶段完成时,可以触发下一个阶段,形成管道(pipeline)。这类似于 Promise 在 JavaScript 中的异步链。
  • 状态:每个实例有三种状态------未完成(pending)、完成(completed,含结果)、失败(failed,含异常)。一旦完成,不可撤销。
2. 执行流程
  1. 创建与启动

    • 使用 supplyAsync(Supplier)runAsync(Runnable) 创建异步任务。
    • 示例:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello Async");
      • supplyAsync 返回结果;runAsync 无返回值(Void)。
  2. 线程池调度

    • 默认使用 ForkJoinPool(工作窃取算法,适合分治任务)。
    • 可自定义:supplyAsync(supplier, executor) 指定 Executor(如 ThreadPoolExecutor)。
  3. 完成与通知

    • 任务执行完,自动设置结果/异常。
    • 通过 get()(阻塞)或链式方法(如 thenApply)获取。
  4. 链式处理

    • 支持函数式接口:thenApply(转换结果)、thenCompose(扁平化嵌套 Future)、thenAccept(消费结果)。
    • 异常传播:用 exceptionallyhandle 处理。
  5. 组合与等待

    • allOf / anyOf:组合多个 Future,等待全部/任一完成。
    • join():非阻塞等价于 get()(抛异常而非检查)。
3. 关键方法分类
类别 方法示例 说明
启动异步 supplyAsync(Supplier<U>) 返回结果的异步任务。
runAsync(Runnable) 无返回值的异步任务。
转换结果 thenApply(Function<T,U>) 上游结果 → 转换 → 新 Future(同步执行)。
thenCompose(Function<T,CompletionStage<U>>) 处理嵌套 Future,扁平化返回。
消费结果 thenAccept(Consumer<T>) 处理结果,无返回。
异常处理 exceptionally(Function<Throwable,U>) 捕获异常,返回默认值。
handle(BiFunction<T,Throwable,U>) 处理结果或异常。
组合 allOf(CompletableFuture<?>...) 等待所有完成,返回 Void Future。
anyOf(CompletableFuture<?>...) 等待第一个完成。
完成控制 complete(T result) 手动完成 Future(用于测试或外部事件)。
  • 同步 vs 异步 :大多数链式方法默认同步执行(在完成线程上),但加 Async 后缀(如 thenApplyAsync)则异步。
4. 简单示例

假设异步读取文件并处理:

java 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AsyncExample {
    public static void main(String[] args) {
        // 启动异步任务:模拟耗时操作
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);  // 模拟2秒延迟
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Hello from Async Thread: " + Thread.currentThread().getName();
        });

        // 链式处理:转换结果 + 异常处理
        CompletableFuture<String> processed = future
            .thenApplyAsync(result -> result.toUpperCase())  // 异步转大写
            .exceptionally(throwable -> "Error occurred: " + throwable.getMessage());  // 异常默认值

        // 等待结果(主线程不阻塞太久)
        String finalResult = processed.join();  // 或 .get()
        System.out.println(finalResult);  // 输出: HELLO FROM ASYNC THREAD: ForkJoinPool.commonPool-worker-1
    }
}
  • 执行流程:主线程立即返回,异步任务在后台运行;链式操作在任务完成后触发。
5. 优点与注意事项
  • 优点
    • 非阻塞:适合高并发(如 Web 服务)。
    • 函数式:减少回调地狱,支持 Lambda。
    • 灵活 :组合任务,易测试(手动 complete)。
  • 注意
    • 线程池溢出:默认池有限制,高负载用自定义 Executor。
    • 异常丢失 :未处理的异常会传播到 get()join(),需显式处理。
    • 版本:Java 8+ 支持,Java 9+ 优化了超时等。

Java 并行流与异步编程概述

Java 中的并行流(Parallel Streams) 是 Java 8 Stream API 的一部分,用于并行处理集合数据(如 List 或数组),它利用多核 CPU 来加速计算。 并行流本质上是异步的 :它会将任务分发到后台线程池(默认是 ForkJoinPool)中执行,这些线程与主线程并发运行,而主线程可以继续其他工作。但注意,并行流的操作整体上是阻塞的 (blocking):如 collect()forEach() 会等待所有子任务完成后再返回结果。

异步编程(Asynchronous Programming) 结合时,并行流常与 CompletableFuture 搭配使用,后者提供更灵活的非阻塞回调链式处理。 这适合 I/O 密集型任务(如网络调用),而非纯 CPU 计算。

1. 并行流的异步机制
  • 如何启用 :在 Stream 上调用 .parallel(),如 list.stream().parallel().forEach(...)
  • 内部实现:基于 Fork/Join 框架,将数据拆分成小块,在多个线程上并行处理。线程池大小 ≈ CPU 核心数 - 1。
  • 异步特性
    • 优点:自动并行,无需手动管理线程;适合简单聚合(如 sum、filter)。
    • 局限:线程间共享状态需小心(避免线程不安全);整体阻塞,不适合长时异步任务。
  • 何时使用:数据量大、操作独立(如 map/reduce),但测试性能(小数据集可能更慢)。

示例(简单并行流):

java 复制代码
import java.util.Arrays;
import java.util.List;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        // 并行计算平方和(异步在后台线程执行,但 main 等待结果)
        int sum = numbers.parallelStream()
                         .mapToInt(n -> n * n)
                         .sum();
        System.out.println("Sum: " + sum);  // 输出: 55
    }
}

这里,mapToInt 在多个线程异步执行,但 sum() 阻塞等待。

2. 并行流与 CompletableFuture 的异步结合
  • 为什么结合 :并行流适合 CPU 密集,并发但阻塞;CompletableFuture 提供真正异步(non-blocking),可链式处理结果。 常见场景:并行流生成多个 Future,然后用 allOf 等待组合。
  • 机制 :在流中用 supplyAsync 包装操作,返回 Future 列表;然后并行处理。

示例(异步并行处理任务):

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class AsyncParallelExample {
    public static void main(String[] args) {
        List<String> tasks = Arrays.asList("Task1", "Task2", "Task3");
        
        // 并行流生成异步任务
        List<CompletableFuture<String>> futures = tasks.parallelStream()
            .map(task -> CompletableFuture.supplyAsync(() -> {
                // 模拟异步 I/O,延迟 1 秒
                try { Thread.sleep(1000); } catch (InterruptedException e) { /* ignore */ }
                return task + " completed asynchronously";
            }))
            .collect(Collectors.toList());
        
        // 等待所有异步任务完成(非阻塞链式)
        CompletableFuture<Void> allDone = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
        allDone.join();  // 阻塞等待,但可在回调中处理
        
        // 获取结果
        futures.stream().map(CompletableFuture::join).forEach(System.out::println);
        // 输出: Task1 completed asynchronously 等
    }
}
  • 执行流程 :并行流分发任务到线程池;每个 supplyAsync 异步运行;allOf 组合等待。
3. 最佳实践与注意事项
  • 性能测试:用 JMH 基准测试并行 vs 顺序。
  • 线程安全 :避免在流中修改共享状态;用 ThreadLocal 或不可变对象。
  • 替代方案:对于复杂异步,考虑 Reactive Streams (Project Reactor) 或 WebFlux。
  • 常见误区:并行流不是"纯异步"------它并行但同步等待结果;用 Future 增强异步性。

CompletableFuture 组合操作示例

CompletableFuture 的组合操作允许你将多个异步任务(CompletableFuture 实例)合并处理,实现复杂的异步流程控制。 核心方法包括 allOf()(等待所有任务完成)和 anyOf()(等待任意一个任务完成),这些方法返回一个新的 CompletableFuture<Void>,便于链式回调。 下面通过 Java 代码示例演示,假设模拟三个异步任务(Task1、Task2、Task3),每个任务延迟 1-3 秒返回字符串结果。

1. allOf() 示例:等待所有任务完成

allOf() 组合多个 Future,只有当所有任务成功完成后,才触发后续操作(如打印结果)。如果任一任务失败,整个组合失败。

java 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AllOfExample {
    public static void main(String[] args) throws Exception {
        // 创建三个异步任务
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            sleep(2);  // 模拟 2 秒任务
            return "Task1 完成";
        });
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
            sleep(1);
            return "Task2 完成";
        });
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
            sleep(3);
            return "Task3 完成";
        });

        // 组合:等待所有完成
        CompletableFuture<Void> allOf = CompletableFuture.allOf(task1, task2, task3);

        // 链式回调:所有完成后获取结果
        allOf.thenRun(() -> {
            try {
                String result1 = task1.get();
                String result2 = task2.get();
                String result3 = task3.get();
                System.out.println(result1 + " | " + result2 + " | " + result3);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).join();  // 等待组合完成

        // 预期输出(总耗时 3 秒):Task1 完成 | Task2 完成 | Task3 完成
    }

    private static void sleep(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
  • 执行流程:任务并行运行,总时间取决于最慢任务(这里 3 秒)。完成后统一打印。
2. anyOf() 示例:等待任意一个任务完成

anyOf() 组合多个 Future,一旦任意一个任务完成(成功或失败),就触发后续操作。适合"竞速"场景,如第一个可用服务器响应。

java 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class AnyOfExample {
    public static void main(String[] args) throws Exception {
        // 创建三个异步任务(模拟不同响应时间)
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            sleep(3);  // 慢任务
            return "Task1 完成";
        });
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
            sleep(1);  // 快任务
            return "Task2 完成";
        });
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
            sleep(2);
            return "Task3 完成";
        });

        // 组合:等待任意一个完成
        CompletableFuture<Object> anyOf = CompletableFuture.anyOf(task1, task2, task3);

        // 链式回调:任意一个完成后处理(返回第一个完成的结果)
        anyOf.thenAccept(result -> {
            System.out.println("第一个完成的任务: " + result);
            // 可继续处理其他任务,如果需要
        }).join();  // 等待任意一个完成

        // 预期输出(总耗时 1 秒):第一个完成的任务: Task2 完成
    }

    private static void sleep(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
  • 执行流程:任务并行,总时间取决于最快任务(这里 1 秒)。其他任务继续后台运行,但回调只基于第一个。
3. 异常处理与组合

在组合中,添加 exceptionally() 处理失败:

java 复制代码
CompletableFuture.allOf(task1, task2, task3)
    .exceptionally(ex -> {
        System.out.println("组合失败: " + ex.getMessage());
        return null;
    });

这确保即使部分任务异常,整个流程可优雅降级。

这些示例展示了组合的强大:allOf 适合批量依赖,anyOf 适合快速响应。实际项目中,可与并行流结合,进一步提升效率。

MES系统中CompletableFuture的业务场景

在制造执行系统(MES)中,异步处理(如CompletableFuture)非常常见,用于应对高并发、生产实时性和跨系统集成等挑战。它能并行执行耗时任务(如数据查询、任务调度),减少响应延迟。下面我基于典型MES场景,列出几个其他业务应用示例(除了你提到的报废率计算)。这些场景多涉及消息队列(如RabbitMQ)或微服务集成,但可以用CompletableFuture实现Java层面的异步优化。 每个示例包括简要描述和伪代码。

1. 生产调度任务分配(高并发场景)
  • 描述:MES需实时调度生产任务(如分配设备、更新库存),但查询设备状态、库存和订单细节是耗时操作。高并发时(如高峰期订单涌入),串行查询会导致延迟。异步并行查询这些数据,然后组合计算最优调度路径,能提升系统吞吐量。

  • CompletableFuture示例 :并行查询设备可用性、库存和订单优先级,等待后计算调度。

    java 复制代码
    // 假设服务类
    public class MesSchedulingService {
        public CompletableFuture<DeviceStatus> queryDeviceAsync(String deviceId) { /* 模拟查询 */ }
        public CompletableFuture<Inventory> queryInventoryAsync(String product) { /* 模拟查询 */ }
        public CompletableFuture<Order> queryOrderAsync(String orderId) { /* 模拟查询 */ }
    
        public void scheduleTask(String deviceId, String product, String orderId) {
            CompletableFuture<DeviceStatus> deviceFuture = queryDeviceAsync(deviceId);
            CompletableFuture<Inventory> inventoryFuture = queryInventoryAsync(product);
            CompletableFuture<Order> orderFuture = queryOrderAsync(orderId);
    
            CompletableFuture.allOf(deviceFuture, inventoryFuture, orderFuture)
                .thenRun(() -> {
                    DeviceStatus device = deviceFuture.join();
                    Inventory inv = inventoryFuture.join();
                    Order order = orderFuture.join();
                    // 计算调度:如果设备可用且库存>阈值,分配任务
                    if (device.isAvailable() && inv.getQuantity() > 100) {
                        assignTask(order.getId(), deviceId);
                        System.out.println("任务调度完成: " + order.getId());
                    }
                }).exceptionally(ex -> {
                    System.err.println("调度失败: " + ex.getMessage());
                    return null;
                });
        }
    }
    • 益处:总时间取决于最慢查询(e.g., 2秒),而非串行4秒。
2. 质量检查与历史数据更新(微服务集成场景)
  • 描述:MES在生产线上进行质量检查后,需要异步更新历史表(如缺陷记录)和通知PLM系统(产品生命周期管理)以优化设计。检查和更新是IO密集型,串行会阻塞生产线监控。 用CompletableFuture并行处理检查结果和通知,确保实时反馈。

  • CompletableFuture示例 :并行保存质量数据和发送PLM更新。

    java 复制代码
    public class QualityCheckService {
        public CompletableFuture<Void> saveHistoryAsync(QualityData data) { /* DB插入 */ }
        public CompletableFuture<Void> notifyPlmAsync(QualityData data) { /* API调用 */ }
    
        public void processCheck(QualityData data) {
            CompletableFuture<Void> historyFuture = saveHistoryAsync(data);
            CompletableFuture<Void> plmFuture = notifyPlmAsync(data);
    
            CompletableFuture.allOf(historyFuture, plmFuture)
                .thenRun(() -> System.out.println("质量检查更新完成"))
                .exceptionally(ex -> {
                    System.err.println("更新失败,回滚: " + ex.getMessage());
                    return null;
                });
        }
    }
    • 益处:解耦检查与更新,适用于延迟不敏感的后台任务,提高MES的整体响应。
3. 跨系统数据同步(对接ERP/WMS场景)
  • 描述:MES需与ERP(企业资源计划)或WMS(仓库管理系统)同步生产数据(如实时库存调整),但对接涉及多API调用。高并发同步时,异步处理能避免瓶颈,确保数据一致性。

  • CompletableFuture示例 :并行同步到ERP和WMS,然后验证一致性。

    java 复制代码
    public class SyncService {
        public CompletableFuture<Void> syncToErpAsync(ProductionData data) { /* API调用 */ }
        public CompletableFuture<Void> syncToWmsAsync(ProductionData data) { /* API调用 */ }
    
        public void syncData(ProductionData data) {
            CompletableFuture<Void> erpFuture = syncToErpAsync(data);
            CompletableFuture<Void> wmsFuture = syncToWmsAsync(data);
    
            CompletableFuture.allOf(erpFuture, wmsFuture)
                .thenRun(() -> {
                    // 验证:查询ERP确认
                    verifyConsistency(data);
                    System.out.println("数据同步成功");
                }).exceptionally(ex -> {
                    System.err.println("同步失败: " + ex.getMessage());
                    return null;
                });
        }
    }
    • 益处:支持MQ消息队列集成(如RabbitMQ),处理高并发跨系统交换。
相关推荐
ChinaRainbowSea3 小时前
11. Spring AI + ELT
java·人工智能·后端·spring·ai编程
不会写DN3 小时前
用户头像文件存储功能是如何实现的?
java·linux·后端·golang·node.js·github
聪明的笨猪猪4 小时前
Java JVM “类加载与虚拟机执行” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
盖世英雄酱581364 小时前
FullGC排查,居然是它!
java·后端
老K的Java兵器库4 小时前
集合性能基准测试报告:ArrayList vs LinkedList、HashMap vs TreeMap、并发 Map 四兄弟
java·开发语言
Knight_AL4 小时前
如何解决 Jacob 与 Tomcat 类加载问题:深入分析 Tomcat 类加载机制与 JVM 双亲委派机制
java·jvm·tomcat
哲学七4 小时前
Springboot3.5.x版本引入javaCv相关库版本问题以及精简引入包
java·ffmpeg
Aqua Cheng.4 小时前
代码随想录第七天|哈希表part02--454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和
java·数据结构·算法·散列表