CompletableFuture 并发陷阱:异步编程中的常见误区

大家好!今天我想和大家聊聊 Java 异步编程中的那些"坑"。如果你正在使用 CompletableFuture,或者打算在项目中引入它,这篇文章绝对不容错过。我会通过实际案例带你避开那些我(和许多开发者)曾经踩过的坑。

1. CompletableFuture 简介

CompletableFuture 是 Java 8 引入的强大异步编程工具,它允许我们通过链式调用处理异步操作。但强大的工具往往伴随着复杂性,使用不当就会引发各种并发问题。

scss 复制代码
                    ┌─────────────┐
                    │    任务A     │
                    └──────┬──────┘
                           │
                           ▼
         ┌─────────────────────────────────┐
         │         CompletableFuture       │
         └─────────────┬───────────────────┘
                       │
           ┌───────────┴───────────┐
           ▼                       ▼
┌─────────────────────┐   ┌─────────────────────┐
│  thenApply(任务B)    │   │  exceptionally(处理)│
└──────────┬──────────┘   └──────────┬──────────┘
           │                         │
           └──────────┬─────────────┘
                      ▼
          ┌──────────────────────┐
          │      最终结果         │
          └──────────────────────┘

2. 陷阱一:忽略异常处理

这是最常见的错误,也是最容易被忽视的。

问题案例

看看这段代码:

java 复制代码
CompletableFuture.supplyAsync(() -> {
    // 模拟从远程服务获取数据
    if (new Random().nextBoolean()) {
        throw new RuntimeException("远程服务调用失败");
    }
    return "数据";
}).thenApply(data -> {
    // 处理数据
    return data.toUpperCase();
}).thenAccept(result -> {
    System.out.println("处理结果: " + result);
});

问题在哪? 如果supplyAsync抛出异常,整个任务链会中断,但程序不会崩溃。更糟糕的是,异常被"吞掉"了,你可能根本不知道发生了什么!

解决方案

  1. 使用 exceptionally 捕获异常:
java 复制代码
CompletableFuture.supplyAsync(() -> {
    // 可能抛出异常的代码
    if (new Random().nextBoolean()) {
        throw new RuntimeException("远程服务调用失败");
    }
    return "数据";
}).thenApply(data -> {
    return data.toUpperCase();
}).exceptionally(ex -> {
    System.err.println("处理异常: " + ex.getMessage());
    return "默认值";  // 提供一个默认值继续链式调用
}).thenAccept(result -> {
    System.out.println("处理结果: " + result);
});
  1. 使用 handle 同时处理正常结果和异常:
java 复制代码
CompletableFuture.supplyAsync(() -> {
    // 可能抛出异常的代码
    if (new Random().nextBoolean()) {
        throw new RuntimeException("远程服务调用失败");
    }
    return "数据";
}).handle((data, ex) -> {
    if (ex != null) {
        System.err.println("处理异常: " + ex.getMessage());
        return "默认值";
    }
    return data.toUpperCase();
}).thenAccept(result -> {
    System.out.println("处理结果: " + result);
});
  1. 使用 whenComplete/whenCompleteAsync 记录日志但不影响结果:
java 复制代码
CompletableFuture.supplyAsync(() -> {
    if (new Random().nextBoolean()) {
        throw new RuntimeException("远程服务调用失败");
    }
    return "数据";
}).whenComplete((result, ex) -> {
    // 不改变结果,只记录状态
    if (ex != null) {
        System.err.println("操作失败,记录异常: " + ex.getMessage());
    } else {
        System.out.println("操作成功,记录结果: " + result);
    }
}).exceptionally(ex -> {
    // 仍然需要处理异常
    return "默认值";
}).thenAccept(result -> {
    System.out.println("最终结果: " + result);
});

whenComplete方法非常适合日志记录、监控和度量收集,它不会改变 CompletableFuture 的结果,但可以观察结果或异常。

真实项目中,你应该根据业务需求决定是否需要默认值,或者进行重试、记录日志等操作。

3. 陷阱二:线程池使用不当

问题案例

java 复制代码
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(2);

List<CompletableFuture<String>> futures = new ArrayList<>();

// 提交10个任务
for (int i = 0; i < 10; i++) {
    int taskId = i;
    futures.add(CompletableFuture.supplyAsync(() -> {
        try {
            // 每个任务耗时较长
            Thread.sleep(1000);
            return "任务" + taskId + "完成";
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }, executor).thenApplyAsync(result -> {
        // 注意这里也使用了同一个线程池
        try {
            Thread.sleep(1000);
            return result + " 处理完成";
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }, executor));
}

// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
executor.shutdown();

问题在哪? 这段代码存在潜在的死锁风险。我们使用了大小为 2 的线程池,但每个任务都会通过thenApplyAsync再次使用相同的线程池提交新任务。当所有线程都被占用,且都在等待队列中的任务完成时,会发生死锁。

重要说明 :如果不指定线程池,thenApplyAsync默认使用ForkJoinPool.commonPool(),这是一个全局共享的线程池。在高负载系统中,这可能导致资源竞争,影响其他使用相同池的任务。

css 复制代码
                ┌─────────────────────────────────────────┐
                │           线程池(2个线程)                 │
                └───────────────────┬─────────────────────┘
                                    │
        ┌───────────────────────────┴───────────────────────┐
        ▼                                                   ▼
┌─────────────────┐                               ┌─────────────────┐
│     线程1        │                               │     线程2        │
└────────┬────────┘                               └────────┬────────┘
         │                                                 │
         ▼                                                 ▼
┌─────────────────┐                               ┌─────────────────┐
│  任务A(supplyAsync)│                               │  任务B(supplyAsync)│
└────────┬────────┘                               └────────┬────────┘
         │                                                 │
         ▼                                                 ▼
┌─────────────────┐                               ┌─────────────────┐
│任务A的thenApplyAsync│◄─── 等待线程 ─── 死锁! ───► │任务B的thenApplyAsync│
└─────────────────┘                               └─────────────────┘

解决方案

  1. 为不同阶段使用不同的线程池:
java 复制代码
// 创建两个线程池
ExecutorService computeExecutor = Executors.newFixedThreadPool(2);
ExecutorService processExecutor = Executors.newFixedThreadPool(2);

List<CompletableFuture<String>> futures = new ArrayList<>();

for (int i = 0; i < 10; i++) {
    int taskId = i;
    futures.add(CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
            return "任务" + taskId + "完成";
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }, computeExecutor).thenApplyAsync(result -> {
        try {
            Thread.sleep(1000);
            return result + " 处理完成";
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }, processExecutor));  // 使用不同的线程池
}

// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
computeExecutor.shutdown();
processExecutor.shutdown();
  1. 使用无界线程池或容量充足的线程池(谨慎使用,资源可能被耗尽):
java 复制代码
// 根据预期负载合理设置线程池大小
ExecutorService executor = Executors.newFixedThreadPool(20);
  1. 使用不带 Async 后缀的方法在同一个线程中执行后续阶段:
java 复制代码
// thenApply而不是thenApplyAsync
futures.add(CompletableFuture.supplyAsync(() -> {
    // 长时间任务
    return "任务完成";
}, executor).thenApply(result -> { // 注意这里没有Async
    // 这部分会在上一步骤的线程中执行,不需要从线程池获取新线程
    return result + " 处理完成";
}));

记住:在决定使用哪种线程池策略时,考虑任务的特性(CPU 密集型还是 IO 密集型),以及系统的整体资源状况。过度竞争共享线程池会导致性能下降。

性能对比

复制代码
【优化前】单线程池 - 10个任务,每个包含两个阶段
┌───────────────────────────────────────────────────────────┐
│                                                           │
│ 执行时间: ~20秒                                            │
│                                                           │
│ 线程池使用率:                                              │
│ ██████████████████████████████████████████████████████████ │
│                                                           │
│ 吞吐量: 0.5任务/秒                                         │
│                                                           │
│ 风险: ⚠️ 高死锁风险                                        │
└───────────────────────────────────────────────────────────┘

【优化后】双线程池 - 同样的10个任务
┌───────────────────────────────────────────────────────────┐
│                                                           │
│ 执行时间: ~10秒                                            │
│                                                           │
│ 线程池使用率:                                              │
│ 计算线程池: ████████████████████████████                   │
│ 处理线程池: ████████████████████████████                   │
│                                                           │
│ 吞吐量: 1.0任务/秒                                         │
│                                                           │
│ 风险: ✓ 无死锁风险                                         │
└───────────────────────────────────────────────────────────┘

4. 陷阱三:超时处理不当

问题案例

java 复制代码
try {
    String result = CompletableFuture.supplyAsync(() -> {
        try {
            // 模拟长时间运行的任务
            Thread.sleep(10000);
            return "任务完成";
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("任务被中断"); // 这行代码永远不会执行
            return "任务中断";
        }
    }).get(5, TimeUnit.SECONDS); // 等待5秒

    System.out.println(result);
} catch (TimeoutException e) {
    System.out.println("任务超时");
}

问题在哪?get()方法超时,会抛出 TimeoutException,但原任务继续在后台运行,浪费系统资源。实际生产中,如果有大量此类超时任务,可能导致线程池资源耗尽。

解决方案

Java 9+中使用orTimeoutcompleteOnTimeout:

java 复制代码
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(10000);
        return "任务完成";
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return "任务中断";
    }
}).orTimeout(5, TimeUnit.SECONDS) // 5秒后抛出异常
  .exceptionally(ex -> {
      if (ex instanceof TimeoutException) {
          return "任务超时,返回默认值";
      }
      return "其他异常: " + ex.getMessage();
  });

String result = future.join();
System.out.println(result);

Java 8 中可以这样实现超时并取消任务:

java 复制代码
ExecutorService executor = Executors.newSingleThreadExecutor();
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(10000);
        return "任务完成";
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        System.out.println("任务被中断了"); // 这次会执行
        return "任务中断";
    }
}, executor);

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> {
    // 5秒后如果任务还没完成,取消它
    boolean canceled = future.cancel(true);
    if (canceled) {
        System.out.println("任务已取消");
    }
}, 5, TimeUnit.SECONDS);

try {
    String result = future.join();
    System.out.println(result);
} catch (CompletionException e) {
    if (e.getCause() instanceof CancellationException) {
        System.out.println("任务被取消");
    } else {
        System.out.println("任务执行异常: " + e.getMessage());
    }
}

executor.shutdown();
scheduler.shutdown();

关于 cancel(boolean mayInterruptIfRunning)的说明

  • cancel(true): 允许中断正在执行的任务。适用于可以安全中断的长时间运行任务。
  • cancel(false): 只取消尚未开始的任务,已经运行的任务会继续执行。适用于不应中断的关键任务,或资源释放依赖于任务正常完成的情况。

选择哪种取消策略取决于你的业务需求和任务特性。

5. 陷阱四:thenApply 与 thenCompose 混淆

问题案例

java 复制代码
CompletableFuture<CompletableFuture<String>> nestedFuture =
    CompletableFuture.supplyAsync(() -> "第一步")
        .thenApply(result ->
            CompletableFuture.supplyAsync(() -> result + " -> 第二步")
        );

// 尝试获取最终结果
CompletableFuture<String> extractedFuture = nestedFuture.join(); // 返回的是CompletableFuture而不是String
String finalResult = extractedFuture.join(); // 需要再次调用join才能获取结果

问题在哪? 使用thenApply处理返回另一个 CompletableFuture 的函数时,会导致 Future 嵌套(CompletableFuture<CompletableFuture>),使代码变得复杂且难以理解。

r 复制代码
使用thenApply导致的嵌套:
┌─────────────────────────────────────────┐
│ CompletableFuture<CompletableFuture<T>> │
│                                         │
│    ┌─────────────────────────────┐      │
│    │     CompletableFuture<T>    │      │
│    │                             │      │
│    │      ┌───────────────┐      │      │
│    │      │       T       │      │      │
│    │      └───────────────┘      │      │
│    └─────────────────────────────┘      │
└─────────────────────────────────────────┘

解决方案

使用thenCompose方法来平展嵌套的 CompletableFuture:

java 复制代码
CompletableFuture<String> flatFuture =
    CompletableFuture.supplyAsync(() -> "第一步")
        .thenCompose(result ->
            CompletableFuture.supplyAsync(() -> result + " -> 第二步")
        );

// 直接获取结果,无需处理嵌套
String finalResult = flatFuture.join();
r 复制代码
使用thenCompose平展结果:
┌─────────────────────────────┐
│     CompletableFuture<T>    │
│                             │
│      ┌───────────────┐      │
│      │       T       │      │
│      └───────────────┘      │
└─────────────────────────────┘

规则很简单

  • 如果你的函数返回值类型 T,使用thenApply
  • 如果你的函数返回 CompletableFuture,使用thenCompose

6. 陷阱五:不当的依赖处理

问题案例

考虑一个获取用户信息的场景,需要并行调用多个服务:

java 复制代码
CompletableFuture<UserProfile> getUserProfile(long userId) {
    CompletableFuture<UserBasicInfo> basicInfoFuture =
        CompletableFuture.supplyAsync(() -> userService.getBasicInfo(userId));

    CompletableFuture<List<Order>> ordersFuture =
        basicInfoFuture.thenCompose(basicInfo ->
            CompletableFuture.supplyAsync(() -> orderService.getOrders(basicInfo.getUserId()))
        );

    CompletableFuture<CreditScore> creditScoreFuture =
        basicInfoFuture.thenCompose(basicInfo ->
            CompletableFuture.supplyAsync(() -> creditService.getScore(basicInfo.getUserId()))
        );

    // 等待所有数据准备好
    return CompletableFuture.allOf(basicInfoFuture, ordersFuture, creditScoreFuture)
        .thenApply(v -> {
            // 所有Future都完成后合并结果
            UserBasicInfo info = basicInfoFuture.join();
            List<Order> orders = ordersFuture.join();
            CreditScore score = creditScoreFuture.join();
            return new UserProfile(info, orders, score);
        });
}

问题在哪? 这段代码看起来合理,但实际上不够高效。ordersFuturecreditScoreFuture都依赖于basicInfoFuture的结果,但它们本身可以并行执行,而不是一个接一个地执行。

解决方案

  1. 重构代码,让独立的查询并行执行:
java 复制代码
CompletableFuture<UserProfile> getUserProfile(long userId) {
    // 先获取基本信息
    CompletableFuture<UserBasicInfo> basicInfoFuture =
        CompletableFuture.supplyAsync(() -> userService.getBasicInfo(userId));

    // 一旦有了基本信息,并行启动其他查询
    CompletableFuture<UserProfile> profileFuture = basicInfoFuture.thenCompose(basicInfo -> {
        // 这两个查询可以并行执行
        CompletableFuture<List<Order>> ordersFuture =
            CompletableFuture.supplyAsync(() -> orderService.getOrders(basicInfo.getUserId()));

        CompletableFuture<CreditScore> creditScoreFuture =
            CompletableFuture.supplyAsync(() -> creditService.getScore(basicInfo.getUserId()));

        // 等待两个并行查询都完成
        return CompletableFuture.allOf(ordersFuture, creditScoreFuture)
            .thenApply(v -> {
                List<Order> orders = ordersFuture.join();
                CreditScore score = creditScoreFuture.join();
                return new UserProfile(basicInfo, orders, score);
            });
    });

    return profileFuture;
}
  1. 使用 thenCombine 合并独立 Future 的结果:

对于两个独立的 Future,可以使用thenCombine方法更优雅地合并结果:

java 复制代码
CompletableFuture<UserProfile> getUserProfile(long userId) {
    // 获取基本信息
    CompletableFuture<UserBasicInfo> basicInfoFuture =
        CompletableFuture.supplyAsync(() -> userService.getBasicInfo(userId));

    // 基于基本信息,获取订单信息
    CompletableFuture<List<Order>> ordersFuture = basicInfoFuture.thenCompose(info ->
        CompletableFuture.supplyAsync(() -> orderService.getOrders(info.getUserId())));

    // 基于基本信息,获取信用评分
    CompletableFuture<CreditScore> creditScoreFuture = basicInfoFuture.thenCompose(info ->
        CompletableFuture.supplyAsync(() -> creditService.getScore(info.getUserId())));

    // 使用thenCombine合并订单和信用评分
    CompletableFuture<CombinedData> combinedDataFuture = ordersFuture.thenCombine(
        creditScoreFuture, (orders, creditScore) -> new CombinedData(orders, creditScore));

    // 最后合并所有数据
    return basicInfoFuture.thenCombine(combinedDataFuture, (info, data) ->
        new UserProfile(info, data.orders, data.creditScore));
}

// Java 14+ 可以使用record简化
record CombinedData(List<Order> orders, CreditScore creditScore) {}

// 或者传统类定义
class CombinedData {
    final List<Order> orders;
    final CreditScore creditScore;

    CombinedData(List<Order> orders, CreditScore creditScore) {
        this.orders = orders;
        this.creditScore = creditScore;
    }
}
markdown 复制代码
优化的执行流程:
┌────────────────┐
│  获取基本信息   │
└────────┬───────┘
         │
         ▼
┌────────────────────────────────────┐
│         获取到用户基本信息          │
└───────────┬─────────────┬──────────┘
            │             │
     并行执行▼             ▼并行执行
┌────────────────┐ ┌────────────────┐
│   获取订单信息  │ │   获取信用评分  │
└────────┬───────┘ └───────┬────────┘
         │                 │
         └──thenCombine────┘
                  ▼
         ┌────────────────┐
         │   组装用户档案  │
         └────────────────┘

性能对比

matlab 复制代码
【优化前】串行依赖处理
┌──────────────────────────────────────────────────────────┐
│                                                          │
│ 执行时间: 基本信息(100ms) + 订单(200ms) + 信用(150ms)      │
│           = 450ms                                        │
│                                                          │
│ API调用时序:                                              │
│ 基本信息: ████████████                                    │
│ 订单信息:             ████████████████████                │
│ 信用评分:                                  ███████████    │
│                                                          │
│ 总响应时间: ~450ms                                        │
└──────────────────────────────────────────────────────────┘

【优化后】并行依赖处理
┌──────────────────────────────────────────────────────────┐
│                                                          │
│ 执行时间: 基本信息(100ms) + max(订单(200ms), 信用(150ms))  │
│           = 300ms                                        │
│                                                          │
│ API调用时序:                                              │
│ 基本信息: ████████████                                    │
│ 订单信息:             ████████████████████                │
│ 信用评分:             ███████████████                     │
│                       [并行执行]                          │
│                                                          │
│ 总响应时间: ~300ms (节省33%)                              │
└──────────────────────────────────────────────────────────┘

对于多个独立的 Future,你也可以考虑使用thenCombine的扩展版本,例如CompletableFuture.allOf(...).thenApply(...)或通过多个thenCombine调用组合结果。

7. 陷阱六:资源泄漏

问题案例

下面的代码试图并行处理多个文件:

java 复制代码
List<CompletableFuture<Long>> futures = new ArrayList<>();

for (File file : files) {
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            return reader.lines().count();
        } catch (IOException e) {
            // 不必要的包装,CompletableFuture会自动处理
            throw new CompletionException(e);
        }
    });
    futures.add(future);
}

// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

问题在哪? 虽然使用了 try-with-resources 确保文件被关闭,但如果任务被取消或超时,无法保证资源被正确释放。另外,异常处理上有不必要的包装,因为 CompletableFuture 会自动将未捕获的异常包装为 CompletionException。

解决方案

  1. 结合 AutoCloseable 接口优雅管理资源:
java 复制代码
class ResourceManager<R extends AutoCloseable, T> {
    private final Function<R, T> processor;
    private final Supplier<R> resourceSupplier;

    public ResourceManager(Supplier<R> resourceSupplier, Function<R, T> processor) {
        this.resourceSupplier = resourceSupplier;
        this.processor = processor;
    }

    public CompletableFuture<T> process() {
        return CompletableFuture.supplyAsync(() -> {
            try (R resource = resourceSupplier.get()) {
                return processor.apply(resource);
            } catch (Exception e) {
                // 直接抛出原始异常,避免不必要的包装
                if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                }
                throw new RuntimeException("处理资源时出错", e);
            }
        });
    }
}

// 使用示例
List<CompletableFuture<Long>> futures = new ArrayList<>();
for (File file : files) {
    ResourceManager<BufferedReader, Long> manager = new ResourceManager<>(
        () -> {
            try {
                return new BufferedReader(new FileReader(file));
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        },
        reader -> reader.lines().count()
    );
    futures.add(manager.process());
}
  1. 使用框架提供的异步资源管理:

如果你使用的是 Quarkus 等现代 Java 框架,可以利用其提供的异步资源管理功能:

java 复制代码
// Quarkus示例
@Asynchronous
public CompletionStage<Long> countLines(File file) {
    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
        return CompletableFuture.completedFuture(reader.lines().count());
    } catch (IOException e) {
        CompletableFuture<Long> future = new CompletableFuture<>();
        future.completeExceptionally(e);
        return future;
    }
}

// 使用
List<CompletionStage<Long>> futures = files.stream()
    .map(this::countLines)
    .collect(Collectors.toList());

CompletableFuture.allOf(futures.stream()
    .map(CompletionStage::toCompletableFuture)
    .toArray(CompletableFuture[]::new))
    .join();
  1. 使用专门的资源处理器:
java 复制代码
class ResourceHandler<T> implements AutoCloseable {
    private final T resource;
    private final Consumer<T> cleanup;

    public ResourceHandler(T resource, Consumer<T> cleanup) {
        this.resource = resource;
        this.cleanup = cleanup;
    }

    public <U> CompletableFuture<U> process(Function<T, U> processor) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return processor.apply(resource);
            } catch (Exception e) {
                // 直接抛出异常,避免不必要的包装
                if (e instanceof RuntimeException) {
                    throw (RuntimeException) e;
                }
                throw new RuntimeException(e);
            }
        });
    }

    @Override
    public void close() {
        cleanup.accept(resource);
    }
}

// 使用并注册shutdown hook
List<ResourceHandler<BufferedReader>> handlers = new ArrayList<>();
List<CompletableFuture<Long>> futures = new ArrayList<>();

for (File file : files) {
    try {
        BufferedReader reader = new BufferedReader(new FileReader(file));
        ResourceHandler<BufferedReader> handler = new ResourceHandler<>(reader, r -> {
            try {
                r.close();
            } catch (IOException e) {
                System.err.println("关闭资源失败: " + e.getMessage());
            }
        });
        handlers.add(handler);
        futures.add(handler.process(r -> r.lines().count()));
    } catch (IOException e) {
        System.err.println("无法打开文件: " + e.getMessage());
    }
}

// 添加shutdown hook确保资源正确释放
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    handlers.forEach(ResourceHandler::close);
}));

通过这些方式,即使在异常情况下,也能确保资源被正确释放。

总结与落地建议

使用 CompletableFuture 时,请记住以下几点:

  1. 始终处理异常 - 使用exceptionallyhandlewhenComplete确保异常不被吞噬
  2. 合理规划线程池 - 为不同类型的任务使用不同的线程池,避免死锁;注意默认线程池的限制
  3. 正确处理超时 - 实现超时机制并确保任务被适当地取消;理解cancel(true)cancel(false)的区别
  4. 理解 API 差异 - 掌握thenApply/thenCompose/thenCombine等方法的区别和适用场景
  5. 并行处理独立任务 - 分析任务依赖关系,最大化并行执行;使用thenCombine合并独立结果
  6. 确保资源释放 - 结合AutoCloseable和 shutdown hook 确保资源在各种情况下都能正确释放
  7. 区分同步和异步变体 - 明确什么时候使用带 Async 后缀的方法
  8. 考虑测试场景 - 编写单元测试验证异步逻辑,包括异常和超时情况
  9. 避免不必要的异常包装 - 了解 CompletableFuture 本身会处理异常包装,避免重复包装

希望这篇文章对你有所帮助!异步编程虽然强大,但需要谨慎使用。通过避开这些常见陷阱,你可以构建更稳定、高效的 Java 应用程序。


感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!

如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~

相关推荐
bobz9654 分钟前
supervisord 的使用
后端
大道无形我有型5 分钟前
📖 Spring 事务机制超详细讲解(哥们专属)
后端
Re2756 分钟前
springboot源码分析--自动配置流程
spring boot·后端
Piper蛋窝9 分钟前
Go 1.2 相比 Go1.1 有哪些值得注意的改动?
后端·go
努力的搬砖人.12 分钟前
java爬虫案例
java·经验分享·后端
海风极客24 分钟前
一文搞懂JSON和HJSON
前端·后端·面试
南雨北斗25 分钟前
2.单独下载和配置PHP环境
后端
海风极客26 分钟前
一文搞懂Clickhouse的MySQL引擎
后端·面试·架构
都叫我大帅哥28 分钟前
遍历世界的通行证:迭代器模式的导航艺术
java·后端·设计模式
yezipi耶不耶1 小时前
Rust入门之迭代器(Iterators)
开发语言·后端·rust