Java并发魔法:CompletableFuture 全解析 CompletableFuture类提供了join()和get() 看这一篇就够了

在Java的世界里,CompletableFuture是异步编程的利器,它为开发者提供了强大的异步处理能力。今天,就让我们一起揭开CompletableFuture的神秘面纱,深入了解它的使用方法、运行原理、应用场景以及源码分析。

2024最全大厂面试题无需C币点我下载或者在网页打开全套面试题已打包

AI绘画关于SD,MJ,GPT,SDXL,Comfyui百科全书

CompletableFuture的使用详解

CompletableFuture是Java 8引入的java.util.concurrent包下的一个类,它实现了Future接口和CompletionStage接口,提供了异步计算的能力。

创建CompletableFuture

import java.util.concurrent.CompletableFuture;

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 异步计算逻辑
    return "Hello, CompletableFuture!";
});

异步执行

future.thenAccept(System.out::println);

结果处理

future.thenApply(s -> s.toUpperCase())
      .thenAccept(System.out::println);

异常处理

future.exceptionally(ex -> {
    // 异常处理逻辑
    return "Error: " + ex.getMessage();
});

串行化执行

future.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"))
      .thenAccept(System.out::println);

并行执行

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2)
      .thenRun(() -> System.out.println("All futures are done."));

选择性执行

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

CompletableFuture<String> result = CompletableFuture.anyOf(future1, future2)
      .thenApply(s -> s + "!");

CompletableFuture的实际项目使用

在实际项目中,CompletableFuture可以用于处理复杂的异步任务流,例如:

  • Web服务:在处理HTTP请求时,可以使用CompletableFuture来异步处理业务逻辑,提高响应速度。
  • 数据处理:在处理大数据时,可以使用CompletableFuture来并行处理数据,加快处理速度。
  • 任务调度:在需要定时执行任务时,可以使用CompletableFuture来实现延迟执行或周期性执行。

CompletableFuture的运行原理

CompletableFuture内部使用了ForkJoinPool来执行异步任务。ForkJoinPool是一个工作窃取(work-stealing)的线程池,它能够有效地利用多核处理器的计算能力。

源码分析

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    // 内部状态变量
    volatile Object result; // Either the result or boxed AltResult
    volatile WaitNode waiters; // Treiber stack for waiting threads

    // 构造函数
    public CompletableFuture() {
        result = new AltResult(null);
    }

    // 异步执行方法
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }

    // 串行化执行方法
    public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }

    // 异常处理方法
    public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {
        return uniExceptionally(fn);
    }

    // 并行执行方法
    public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
        return andTree(cfs, 0, cfs.length - 1);
    }

    // 选择性执行方法
    public static <U> CompletableFuture<U> anyOf(CompletableFuture<U>... cfs) {
        return orTree(cfs, 0, cfs.length - 1);
    }

    // 其他方法...
}

在上述代码中,CompletableFuture类的构造函数初始化了一个内部状态变量result,用于存储计算结果或异常信息。supplyAsync方法用于异步执行任务,thenApply方法用于对结果进行转换,exceptionally方法用于处理异常,allOfanyOf方法用于组合多个CompletableFuture

CompletableFuture在高并发场景下的表现通常是非常出色的,因为它利用了Java 8引入的ForkJoinPool,这是一个工作窃取(work-stealing)的线程池,能够有效地利用多核处理器的计算能力。CompletableFuture通过异步执行和任务组合,可以显著提高程序的吞吐量和响应速度。

高并发场景下的表现特点:

  1. 异步执行CompletableFuture允许开发者以异步的方式执行任务,这意味着任务可以在不同的线程上并行执行,从而减少了等待时间,提高了程序的响应速度。

  2. 任务组合CompletableFuture提供了多种方法来组合多个异步任务,如thenApplythenComposethenCombine等。这些组合操作可以在任务完成时自动触发,减少了手动管理线程的复杂性。

  3. 线程池管理CompletableFuture默认使用ForkJoinPool.commonPool(),这是一个共享的线程池,它会根据CPU核心数自动调整线程数量。在高并发场景下,ForkJoinPool能够有效地管理线程,避免线程过多导致的上下文切换开销。

  4. 无阻塞设计CompletableFuture的设计是无阻塞的,这意味着它不会因为等待其他任务的完成而阻塞当前线程。相反,它会注册回调,当任务完成时,回调会被执行。

  5. 异常处理CompletableFuture提供了强大的异常处理机制,可以方便地处理异步任务中可能出现的异常,避免异常导致的程序崩溃。

  6. 性能优化:在高并发场景下,CompletableFuture可以利用ForkJoinPool的并行执行能力,减少任务执行的总时间,提高程序的整体性能。

注意事项:

  • 资源限制:尽管CompletableFuture在高并发场景下表现良好,但仍然受到系统资源的限制。如果并发任务过多,可能会导致内存不足或线程过多导致的性能下降。

  • 任务依赖:在使用CompletableFuture组合任务时,需要注意任务之间的依赖关系。不恰当的任务依赖可能会导致死锁或性能瓶颈。

  • 异常处理:在高并发场景下,异常处理尤为重要。确保所有的异常都被适当地捕获和处理,以避免程序崩溃。

  • 测试:在高并发场景下,应该对CompletableFuture的使用进行充分的测试,以确保在高负载下的稳定性和性能。

通过上述特点和注意事项,我们可以看出CompletableFuture在高并发场景下是一个非常有用的工具。它能够帮助开发者构建出高性能、高吞吐量的并发应用程序。然而,为了确保最佳性能,开发者需要合理设计任务的并发执行,并进行充分的测试和优化。

CompletableFuture类提供了许多方法来处理异步计算的结果,包括执行异步任务、组合任务、处理异常和完成回调等。以下是一些常用方法的示例代码:

创建异步任务

import java.util.concurrent.CompletableFuture;

// 使用supplyAsync创建异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000); // 模拟耗时操作
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return "Hello, CompletableFuture!";
});

// 使用runAsync创建异步任务(无返回值)
CompletableFuture<Void> futureVoid = CompletableFuture.runAsync(() -> {
    // 执行一些操作
});

结果处理

// thenApply方法:对结果进行转换
future.thenApply(s -> s.toUpperCase())
      .thenAccept(System.out::println);

// thenAccept方法:消费结果
future.thenAccept(s -> System.out.println("Processed result: " + s));

// thenRun方法:执行一个动作,不关心结果
future.thenRun(() -> System.out.println("Task completed."));

异常处理

// exceptionally方法:处理异常
future.exceptionally(ex -> {
    System.out.println("Exception occurred: " + ex.getMessage());
    return "Error!";
}).thenAccept(System.out::println);

任务组合

// thenCompose方法:将一个异步任务的结果作为另一个异步任务的输入
CompletableFuture<String> futureCompose = future.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));

// thenCombine方法:组合两个异步任务的结果
CompletableFuture<String> futureCombine = future.thenCombine(
    CompletableFuture.supplyAsync(() -> " World"),
    (s1, s2) -> s1 + s2
);

// allOf方法:等待所有给定的CompletableFuture实例完成
CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(future, futureVoid);

// anyOf方法:等待任何一个给定的CompletableFuture实例完成
CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future, futureVoid);

完成回调

// whenComplete方法:在任务完成时执行回调,无论成功或失败
future.whenComplete((result, ex) -> {
    if (ex == null) {
        System.out.println("Result: " + result);
    } else {
        System.out.println("Exception: " + ex.getMessage());
    }
});

// handle方法:在任务完成时执行回调,无论成功或失败,并可以修改结果
future.handle((result, ex) -> {
    if (ex == null) {
        return result.toUpperCase();
    } else {
        return "Error: " + ex.getMessage();
    }
}).thenAccept(System.out::println);

示例:链式调用

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000); // 模拟耗时操作
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return "Hello, CompletableFuture!";
}).thenApply(s -> s.toUpperCase())
  .thenAccept(System.out::println)
  .exceptionally(ex -> {
      System.out.println("Exception occurred: " + ex.getMessage());
      return "Error!";
  });

以上代码展示了CompletableFuture的一些基本用法,包括创建异步任务、处理结果、异常处理、任务组合和完成回调。在实际项目中,可以根据具体需求选择合适的方法来构建复杂的异步任务流。

CompletableFuture能够处理在异步任务执行过程中抛出的异常。这些异常可以是由于代码逻辑错误、资源访问问题、外部服务失败等原因引起的。CompletableFuture提供了几种方式来处理这些异常:

  1. exceptionally方法:这个方法允许你指定一个函数,该函数在CompletableFuture完成时(无论是正常完成还是异常完成)被调用。如果CompletableFuture在执行过程中抛出异常,exceptionally方法中的函数将被调用,并且可以返回一个替代的结果。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        // 模拟异常情况
        throw new RuntimeException("Something went wrong");
    }).exceptionally(ex -> {
        // 处理异常
        System.out.println("Exception occurred: " + ex.getMessage());
        return "Error!";
    });
    
  2. handle方法handle方法允许你指定一个函数,该函数在CompletableFuture完成时(无论是正常完成还是异常完成)被调用。与exceptionally不同的是,handle方法可以同时处理正常结果和异常。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        // 模拟异常情况
        throw new RuntimeException("Something went wrong");
    }).handle((result, ex) -> {
        if (ex != null) {
            // 处理异常
            System.out.println("Exception occurred: " + ex.getMessage());
            return "Error!";
        } else {
            // 处理正常结果
            return result;
        }
    });
    
  3. whenComplete方法whenComplete方法类似于handle方法,它允许你指定一个函数,在CompletableFuture完成时(无论是正常完成还是异常完成)被调用。与handle不同的是,whenComplete不返回结果。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        // 模拟异常情况
        throw new RuntimeException("Something went wrong");
    }).whenComplete((result, ex) -> {
        if (ex != null) {
            // 处理异常
            System.out.println("Exception occurred: " + ex.getMessage());
        } else {
            // 处理正常结果
            System.out.println("Result: " + result);
        }
    });
    
  4. CompletableFuture的链式调用:在链式调用中,如果任何一个CompletableFuture抛出异常,整个链式调用将停止,并且异常将被传递到链的下一个exceptionallyhandle方法。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        // 模拟异常情况
        throw new RuntimeException("Something went wrong");
    }).thenApply(s -> s.toUpperCase())
      .exceptionally(ex -> {
          // 处理异常
          System.out.println("Exception occurred: " + ex.getMessage());
          return "Error!";
      });
    

在使用CompletableFuture时,应该注意异常处理的重要性。合理的异常处理可以确保程序的健壮性,避免因为异常导致的程序崩溃或数据不一致。此外,异常处理还可以提供更友好的用户反馈,帮助用户理解发生了什么问题。

CompletableFuture的链式调用是其最强大的特性之一,它允许你将多个异步操作串联起来,形成一个异步任务链。每个操作的结果可以作为下一个操作的输入,从而实现复杂的异步处理流程。

链式调用的工作原理是基于CompletableFuturethenApplythenAcceptthenRunthenCompose等方法。这些方法允许你指定一个函数,该函数在前一个CompletableFuture完成时被调用,并且可以返回一个新的CompletableFuture

以下是链式调用的基本步骤:

  1. 创建初始CompletableFuture:使用supplyAsyncrunAsync方法创建一个CompletableFuture实例,该实例将异步执行一个任务。

  2. 添加后续操作:使用thenApplythenAcceptthenRunthenCompose等方法将后续操作添加到链中。这些方法接受一个函数作为参数,该函数将前一个CompletableFuture的结果作为输入,并返回一个新的CompletableFuture

  3. 处理结果或异常:在链的末尾,你可以使用thenAcceptthenRunthenApply来处理最终结果,或者使用exceptionally来处理异常。

  4. 获取最终结果:链式调用完成后,你可以使用get方法来获取最终结果。如果链中任何一个CompletableFuture抛出异常,get方法将抛出ExecutionException

下面是一个简单的链式调用示例:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureChainExample {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 模拟耗时操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Hello";
        }).thenApply(s -> s + " World")
          .thenApply(s -> s.toUpperCase())
          .thenAccept(System.out::println)
          .exceptionally(ex -> {
              System.out.println("Exception occurred: " + ex.getMessage());
              return null;
          });

        // 阻塞等待结果
        future.join();
    }
}

在这个例子中,我们首先创建了一个异步任务,该任务返回字符串"Hello"。然后,我们使用thenApply方法将字符串转换为大写,并使用thenAccept方法打印结果。如果在链中发生异常,exceptionally方法将捕获异常并打印错误信息。

链式调用使得CompletableFuture非常适合构建复杂的异步处理流程,它能够帮助开发者以声明式的方式编写异步代码,从而提高代码的可读性和可维护性。

处理多个CompletableFuture的依赖关系通常涉及以下几种情况:

  1. 顺序依赖:一个CompletableFuture的结果是另一个CompletableFuture的输入。
  2. 并行依赖:多个CompletableFuture可以并行执行,但需要等待它们全部完成后再继续。
  3. 条件依赖:一个CompletableFuture的结果决定了是否执行另一个CompletableFuture

针对这些情况,CompletableFuture提供了不同的方法来处理依赖关系:

1. 顺序依赖

使用thenApplythenAcceptthenRun方法来处理顺序依赖。这些方法允许你指定一个函数,该函数在前一个CompletableFuture完成时被调用,并且可以返回一个新的CompletableFuture

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    return "Result1";
});

CompletableFuture<String> future2 = future1.thenApply(result1 -> {
    // 使用future1的结果
    return "Result2";
});

2. 并行依赖

使用allOfanyOf方法来处理并行依赖。allOf方法等待所有给定的CompletableFuture实例完成,而anyOf方法等待任何一个给定的CompletableFuture实例完成。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    return "Result1";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    return "Result2";
});

CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);

allFutures.thenRun(() -> {
    // 所有future都已完成
});

3. 条件依赖

使用whenCompletehandle方法来处理条件依赖。这些方法允许你指定一个函数,该函数在CompletableFuture完成时(无论是正常完成还是异常完成)被调用,并且可以返回一个新的CompletableFuture

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    return "Result1";
});

CompletableFuture<String> future2 = future1.thenApply(result1 -> {
    // 使用future1的结果
    return "Result2";
}).whenComplete((result, ex) -> {
    if (ex == null) {
        // 处理正常结果
    } else {
        // 处理异常
    }
});

4. 组合依赖

使用thenCompose方法来组合依赖。thenCompose方法允许你将一个CompletableFuture的结果作为另一个异步任务的输入。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    return "Result1";
});

CompletableFuture<String> future2 = future1.thenCompose(result1 -> {
    // 使用future1的结果
    return CompletableFuture.supplyAsync(() -> {
        return "Result2";
    });
});

5. 错误处理

使用exceptionally方法来处理错误。exceptionally方法允许你指定一个函数,该函数在CompletableFuture抛出异常时被调用,并且可以返回一个替代的结果。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    return "Result1";
}).exceptionally(ex -> {
    // 处理异常
    return "Error";
});

通过上述方法,你可以灵活地处理CompletableFuture之间的依赖关系,构建出复杂的异步处理流程。在实际应用中,根据具体的业务逻辑和需求选择合适的方法来实现依赖关系的处理。

CompletableFuture类提供了join()get()两种方法来获取异步计算的结果。尽管它们都可以用来获取CompletableFuture的计算结果,但它们在行为和使用场景上有所不同。

get()方法

get()方法是Future接口中的一个方法,它阻塞当前线程直到Future完成。如果Future正常完成,get()方法将返回计算结果;如果Future被取消,它将抛出CancellationException;如果Future抛出异常,它将抛出ExecutionException

try {
    Object result = future.get();
    // 使用结果
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    // 处理中断异常
} catch (ExecutionException e) {
    Throwable cause = e.getCause();
    // 处理计算过程中抛出的异常
}

join()方法

join()方法是CompletableFuture特有的方法,它也阻塞当前线程直到CompletableFuture完成。如果CompletableFuture正常完成,join()方法将返回计算结果;如果CompletableFuture被取消或抛出异常,它将抛出CompletionException,这是一个未检查异常,它包装了原始的异常。

try {
    Object result = future.join();
    // 使用结果
} catch (CompletionException e) {
    Throwable cause = e.getCause();
    // 处理计算过程中抛出的异常
}

区别

  • 异常处理get()方法抛出的是ExecutionException,而join()方法抛出的是CompletionExceptionCompletionExceptionExecutionException的子类,它提供了更多的信息,例如原始异常的类型和消息。
  • 中断处理get()方法在被中断时会抛出InterruptedException,而join()方法不会。如果你需要处理中断,应该使用get()方法。
  • 使用场景join()方法通常用于CompletableFuture链式调用中,因为它抛出的CompletionException可以被链式调用中的exceptionally方法捕获和处理。而get()方法通常用于需要捕获中断异常的场景。

示例

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 模拟耗时操作
    return "Result";
});

try {
    String result = future.get(); // 使用get()方法
    System.out.println(result);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
    System.out.println("Interrupted");
} catch (ExecutionException e) {
    Throwable cause = e.getCause();
    System.out.println("Exception occurred: " + cause.getMessage());
}

try {
    String result = future.join(); // 使用join()方法
    System.out.println(result);
} catch (CompletionException e) {
    Throwable cause = e.getCause();
    System.out.println("Exception occurred: " + cause.getMessage());
}

在实际使用中,根据你的需求选择合适的方法。如果你需要处理中断异常,或者你的代码需要与旧的Future接口兼容,那么get()方法可能是更好的选择。如果你在CompletableFuture链式调用中工作,并且希望利用CompletionException的特性,那么join()方法可能更适合你。

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值