1. Introduction
1.导言
In the CompletableFuture framework, thenApply() and thenApplyAsync() are crucial methods that facilitate asynchronous programming.
在 CompletableFuture 框架中,thenApply() 和 thenApplyAsync() 是促进异步编程的关键方法。
In this tutorial, we’ll delve into the differences between thenApply() and thenApplyAsync() in CompletableFuture. We’ll explore their functionalities, use cases, and when to choose one over the other.
在本教程中,我们将深入探讨 CompletableFuture 中 thenApply() 和 thenApplyAsync() 之间的区别。我们将探讨它们的功能、用例以及何时选择其一。
2. Understanding thenApply() and thenApplyAsync()
2.了解 thenApply() 和 thenApplyAsync()
CompletableFuture provides the methods thenApply() and thenApplyAsync() for applying transformations to the result of a computation. Both methods enable chaining operations to be performed on the result of a CompletableFuture.
CompletableFuture 提供了 thenApply() 和 thenApplyAsync() 方法,用于对计算结果应用转换。这两个方法可在 CompletableFuture 的结果上执行连锁操作。
2.1. thenApply()
2.1.然后应用()</em
thenApply() is a method used to apply a function to the result of a CompletableFuture when it completes. It accepts a Function functional interface, applies the function to the result, and returns a new CompletableFuture with the transformed result.
thenApply() 是一种方法,用于在 CompletableFuture 完成时将函数应用到其结果。它接受一个 Function 函数接口,将函数应用于结果,并返回一个带有转换后结果的新 CompletableFuture 。
2.2. thenApplyAsync()
2.2.thenApplyAsync()
thenApplyAsync() is a method that executes the provided function asynchronously. It accepts a Function functional interface and an optional Executor and returns a new CompletableFuture with the asynchronously transformed result.
thenApplyAsync() 是一种异步执行所提供函数的方法。它接受一个 Function 功能接口和一个可选的 Executor 并返回一个带有异步转换结果的新 CompletableFuture 。
3. Execution Thread
3.执行线程
The primary difference between thenApply() and thenApplyAsync() lies in their execution behavior.
thenApply() 和 thenApplyAsync() 之间的主要区别在于它们的执行行为。
3.1. thenApply()
3.1.然后应用()</em
By default, thenApply() method executes the transformation function using the same thread that completed the current CompletableFuture. This means that the execution of the transformation function may occur immediately after the result becomes available. This can potentially block the thread if the transformation function is long-running or resource-intensive.
默认情况下,thenApply() 方法使用完成当前 CompletableFuture 的同一线程执行转换函数。这意味着转换函数可能会在结果可用后立即执行。如果转换函数长时间运行或需要大量资源,这可能会阻塞线程。
However, if we call thenApply() on a CompletableFuture that hasn’t yet completed, it executes the transformation function asynchronously in another thread from the executor pool.
但是,如果我们在尚未完成的 CompletableFuture 上调用 thenApply(),它将在 执行器池中的另一个线程中异步执行转换函数。
Here’s a code snippet illustrating thenApply():
下面是说明 thenApply() 的代码片段:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> thenApplyResultFuture = future.thenApply(num -> "Result: " + num);
String thenApplyResult = thenApplyResultFuture.join();
assertEquals("Result: 5", thenApplyResult);
In this example, if the result is already available and the current thread is compatible, thenApply() might execute the function synchronously. However, it’s important to note that CompletableFuture intelligently decides whether to execute synchronously or asynchronously based on various factors, such as the availability of the result and the threading context.
在此示例中,如果结果已经可用,并且当前线程是兼容的,那么Apply()可能会同步执行函数。但是,需要注意的是,CompletableFuture 会根据各种因素(例如结果的可用性和线程上下文)智能地决定是同步执行还是异步执行。
3.2. thenApplyAsync()
3.2.thenApplyAsync()
In contrast, thenApplyAsync() guarantees asynchronous execution of the provided function by utilizing a thread from an executor pool, typically the ForkJoinPool.commonPool(). This ensures that the function is executed asynchronously and may run in a separate thread, preventing any blocking of the current thread.
与此相反,thenApplyAsync() 通过使用执行池(通常是 ForkJoinPool.commonPool() )中的线程,保证了所提供函数的异步执行。 这确保了函数的异步执行,并可在单独的线程中运行,从而避免了对当前线程的任何阻塞。
Here’s how we can use thenApplyAsync():
以下是我们如何使用 thenApplyAsync() 的方法:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> thenApplyAsyncResultFuture = future.thenApplyAsync(num -> "Result: " + num);
String thenApplyAsyncResult = thenApplyAsyncResultFuture.join();
assertEquals("Result: 5", thenApplyAsyncResult);
In this example, even if the result is immediately available, thenApplyAsync() always schedules the function for asynchronous execution on a separate thread.
在此示例中,即使结果立即可用,thenApplyAsync() 也总是调度函数在单独的线程上异步执行。
4. Control Thread
4.控制线
While both thenApply() and thenApplyAsync() enable asynchronous transformations, they differ in their support for specifying custom executors and thus controlling the execution thread.
虽然 thenApply() 和 thenApplyAsync() 都支持异步转换,但它们在支持指定自定义执行器从而控制执行线程方面有所不同。
4.1. thenApply()
4.1.thenApply()
The thenApply() method doesn’t directly support specifying a custom executor to control the execution thread. It relies on the default behavior of CompletableFuture, which may execute the transformation function on the same thread that completed the previous stage, typically a thread from the common pool.
thenApply() 方法不直接支持指定自定义执行器来控制执行线程。它依赖于 CompletableFuture 的默认行为,该行为可能会在完成前一阶段的同一线程(通常是公共池中的线程)上执行转换函数。
4.2. thenApplyAsync()
4.2.thenApplyAsync()
In contrast, thenApplyAsync() allows us to explicitly specify an executor to control the execution thread. By providing a custom executor, we can dictate where the transformation function executes, enabling more precise thread management.
相比之下,thenApplyAsync() 允许我们显式指定一个执行器来控制执行线程。通过提供自定义执行器,我们可以指定转换函数的执行位置,从而实现更精确的线程管理。
Here’s how we can use a custom executor with thenApplyAsync():
下面介绍如何使用 thenApplyAsync() 来使用自定义执行器:
ExecutorService customExecutor = Executors.newFixedThreadPool(4);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 5;
}, customExecutor);
CompletableFuture<String> resultFuture = future.thenApplyAsync(num -> "Result: " + num, customExecutor);
String result = resultFuture.join();
assertEquals("Result: 5", result);
customExecutor.shutdown();
In this example, a custom executor with a fixed thread pool size of 4 is created. The thenApplyAsync() method then uses this custom executor, providing control over the execution thread for the transformation function.
在此示例中,创建了一个线程池大小固定为 4 的自定义执行器。然后, thenApplyAsync() 方法使用该自定义执行器,为转换函数提供执行线程控制。
5. Exception Handling
5.异常处理
The key difference in exception handling between thenApply() and thenApplyAsync() lies in when and how the exception becomes visible.
thenApply() 和henApplyAsync()在异常处理方面的主要区别在于异常何时以及如何变得可见。
5.1. thenApply()
5.1.thenApply()
If the transformation function provided to thenApply() throws an exception, the thenApply() stage immediately completes the CompletableFuture exceptionally. This exceptional completion carries the thrown exception within a CompletionException, wrapping the original exception.
如果提供给 thenApply() 的转换函数抛出异常,thenApply() 阶段将立即完成 CompletableFuture 异常。这种异常完成会将抛出的异常带入 CompletionException 中,并封装原始异常。
Let’s illustrate this with an example:
让我们举例说明一下:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> resultFuture = future.thenApply(num -> "Result: " + num / 0);
assertThrows(CompletionException.class, () -> resultFuture.join());
In this example, we’re trying to divide 5 by 0, which results in an ArithmeticException being thrown. This CompletionException is directly propagated to the next stage or the caller, meaning any exception within the function is immediately visible for handling. So, if we try to access the result using methods like get(), join(), or thenAccept(), we encounter CompletionException directly:
在这个示例中,我们试图用 5 除以 0,结果抛出了一个 ArithmeticException 异常。这种 CompletionException 会直接传播到下一阶段或调用者,这意味着函数中的任何异常都会立即显现,以供处理。 因此,如果我们尝试使用 get()、join() 或 thenAccept() 等方法访问结果,我们就会直接遇到 CompletionException 异常:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> resultFuture = future.thenApply(num -> "Result: " + num / 0);
try {
// Accessing the result using join()
String result = resultFuture.join();
assertEquals("Result: 5", result);
} catch (CompletionException e) {
assertEquals("java.lang.ArithmeticException: / by zero", e.getMessage());
}
In this example, the exception thrown during the function passed to thenApply(). The stage recognizes the problem and wraps the original exception in a CompletionException, allowing us to handle it further down the line.
在这个示例中,异常是在传递给 thenApply() 的函数过程中抛出的。 该阶段识别出了问题,并将原始异常封装在 CompletionException 中,使我们能够进一步处理该异常。
5.2. thenApplyAsync()
5.2.thenApplyAsync()
While the transformation function runs asynchronously, any exception within it isn’t directly propagated to the returned CompletableFuture. The exception isn’t immediately visible when we call methods like get(), join(), or thenAccept(). These methods block until the asynchronous operation finishes, potentially leading to deadlock if not handled correctly.
虽然转换函数是异步运行的,但其中的任何异常都不会直接传播到返回的 CompletableFuture 中。当我们调用 get()、join() 或 thenAccept() 等方法时,异常不会立即显现。这些方法会阻塞,直到异步操作结束,如果处理不当,可能会导致死锁。
To handle exceptions in thenApplyAsync(), we must use dedicated methods like handle(), exceptionally(), or whenComplete(). These methods allow us to intercept and process the exception when it occurs asynchronously.
要在 thenApplyAsync() 中处理异常,我们必须使用专门的方法,如 handle()、exceptionally()、 或 whenComplete() 。这些方法允许我们在异常异步发生时拦截并处理异常。
Here is the code snippet to demonstrate explicit handling with handle:
下面是演示使用句柄进行显式处理的代码片段:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<String> thenApplyAsyncResultFuture = future.thenApplyAsync(num -> "Result: " + num / 0);
String result = thenApplyAsyncResultFuture.handle((res, error) -> {
if (error != null) {
// Handle the error appropriately, e.g., return a default value
return "Error occurred";
} else {
return res;
}
})
.join(); // Now join() won't throw the exception
assertEquals("Error occurred", result);
In this example, even though an exception occurred within thenApplyAsync(), it isn’t directly visible in the resultFuture. The join() method blocks and eventually unwraps the CompletionException, revealing the original ArithmeticException.
在此示例中,即使 thenApplyAsync() 中发生了异常, resultFuture 中也无法直接看到该异常。join()方法阻塞并最终解开了CompletionException,显示了最初的ArithmeticException。
6. Use Case
6.用例
In this section, we’ll explore common use cases for thenApply() and thenApplyAsync() methods in CompletableFuture.
在本节中,我们将探讨 CompletableFuture 中 thenApply() 和 thenApplyAsync() 方法的常见用例。
6.1. thenApply()
6.1.thenApply()
The thenApply() method is particularly useful in the following scenarios:
在以下情况中,thenApply() 方法尤其有用:
- Sequential Transformation: When there is a need to sequentially apply a transformation to the result of a CompletableFuture. This could involve tasks such as converting a numeric result to a string or performing calculations based on the result.
- Lightweight Operations: It’s well-suited for executing small, quick transformations that won’t impose significant blocking on the calling thread. Examples include converting numbers to strings, performing calculations based on the result, or manipulating data structures.
6.2. thenApplyAsync()
6.2.thenApplyAsync()
On the other hand, the thenApplyAsync() method is appropriate in the following situations:
另一方面,thenApplyAsync() 方法适用于以下情况:
- Asynchronous Transformation: When there’s a requirement to apply a transformation asynchronously, possibly leveraging multiple threads for parallel execution. For instance, in a web application where users upload images for editing, employing asynchronous transformation with CompletableFuture can be beneficial for concurrently applying resizing, filters, and watermarks, thus enhancing processing efficiency and user experience.
- Blocking Operations: In cases where the transformation function involves blocking operations, I/O operations, or computationally intensive tasks, thenApplyAsync() becomes advantageous. By offloading such computations to a separate thread, helps prevent blocking the calling thread, thereby ensuring smoother application performance.
7. Summary
7.摘要
Here’s a summary table comparing the key differences between thenApply() and thenApplyAsync().
下面是一个汇总表,比较了 thenApply() 和 thenApplyAsync() 之间的主要区别。
Feature | thenApply() | thenApplyAsync() |
---|---|---|
Execution Behavior | Same thread as the previous stage or a separate thread from the executor pool (if called before completion) | Separate thread from executor pool |
Custom Executor Support | Not supported | Supports custom executor for thread control |
Exception Handling | Immediately propagates exception within CompletionException | Exception not directly visible requires explicit handling |
Performance | May block calling thread | Avoids blocking and enhances performance |
Use Cases | Sequential transformations, lightweight operations | Asynchronous transformations, blocking operations |
8. Conclusion
8.结论
In this article, we’ve explored the functionalities and differences between the thenApply() and thenApplyAsync() methods in the CompletableFuture framework.
在本文中,我们探讨了 CompletableFuture 框架中 thenApply() 和 thenApplyAsync() 方法的功能和区别。
thenApply() may potentially block the thread, making it suitable for lightweight transformations or scenarios where synchronous execution is acceptable. On the other hand, thenApplyAsync() guarantees asynchronous execution, making it ideal for operations involving potential blocking or computationally intensive tasks where responsiveness is critical.
thenApply()可能会阻塞线程,因此适用于轻量级转换或可接受同步执行的应用场景。另一方面,thenApplyAsync() 可保证异步执行,因此非常适合涉及潜在阻塞的操作或计算密集型任务(响应速度至关重要)。
As always, the source code for the examples is available over on GitHub.
与往常一样,这些示例的源代码可在 GitHub 上获取。