1. Introduction
1.导言
Java’s CompletableFuture framework provides powerful asynchronous programming capabilities, facilitating the execution of tasks concurrently.
Java 的 CompletableFuture 框架提供了强大的异步编程功能,便于并发执行任务。
In this tutorial, we’ll delve into two essential methods offered by CompletableFuture – runAsync() and supplyAsync(). We’ll explore their differences, use cases, and when to choose one over the other.
在本教程中,我们将深入研究 CompletableFuture 提供的两个基本方法–runAsync() 和 supplyAsync() 。我们将探讨它们的区别、用例以及何时选择其中一种方法。
2. Understanding CompletableFuture, runAsync() and supplyAsync()
了解 CompletableFuture、runAsync() 和 supplyAsync()
CompletableFuture is a powerful framework in Java that enables asynchronous programming, facilitating the execution of tasks concurrently without blocking the main thread. runAsync() and supplyAsync() are methods provided by the CompletableFuture class.
CompletableFuture是 Java 中的一个强大框架,可实现异步编程,在不阻塞主线程的情况下促进任务的并发执行。
Before we dive into a comparison, let’s understand the individual functionalities of runAsync() and supplyAsync(). Both methods initiate asynchronous tasks, allowing us to execute code concurrently without blocking the main thread.
在深入比较之前,让我们先了解一下 runAsync() 和 supplyAsync() 的各自功能。这两种方法都会启动异步任务,允许我们在不阻塞主线程的情况下并发执行代码。
runAsync() is a method used to execute a task asynchronously that doesn’t produce a result. It’s suitable for fire-and-forget tasks where we want to execute code asynchronously without waiting for a result. For example, logging, sending notifications, or triggering background tasks.
runAsync()是一种用于异步执行不产生结果的任务的方法。它适用于 “fire-and-forget “任务,我们希望在不等待结果的情况下异步执行代码。例如,记录日志、发送通知或触发后台任务。
On the other hand, supplyAsync() is a method used to asynchronously execute a task that produces a result. It’s ideal for tasks that require a result for further processing. For example, fetching data from a database, making an API call, or performing a computation asynchronously.
另一方面,supplyAsync() 是一种用于异步执行产生结果的任务的方法。例如,从数据库中获取数据、进行 API 调用或异步执行计算。
3. Input and Return
3.输入和返回
The main difference between runAsync() and supplyAsync() lies in the input they accept and the type of return value they produce.
runAsync() 和 supplyAsync() 之间的主要区别在于它们接受的输入和产生的返回值类型。
3.1. runAsync()
3.1 runAsync()
The runAsync() method is employed when the asynchronous task to be executed doesn’t produce any result. It accepts a Runnable functional interface and initiates the task asynchronously. It returns a CompletableFuture<Void> and is useful for scenarios where the focus is on the completion of a task rather than obtaining a specific result.
当要执行的异步任务没有产生任何结果时,就会使用 runAsync() 方法。它接受一个 Runnable 功能接口,并异步启动任务。它返回一个CompletableFuture<Void>,适用于重点是完成任务而不是获得特定结果的场景。
Here’s a snippet showcasing its usage:
下面是展示其用法的片段:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// Perform non-result producing task
System.out.println("Task executed asynchronously");
});
In this example, the runAsync() method is used to execute a non-result producing task asynchronously. The provided lambda expression encapsulates the task to be executed. Upon completion, it prints:
在本例中,runAsync() 方法用于异步执行一个不产生结果的任务。所提供的 lambda 表达式封装了要执行的任务。完成后,它将打印
Task completed successfully
3.2. supplyAsync()
3.2.supplyAsync()
On the other hand, supplyAsync() is employed when the asynchronous task yields a result. It accepts a Supplier functional interface and initiates the task asynchronously. Subsequently, it returns a CompletableFuture<T>, where T is the type of the result produced by the task.
另一方面,supplyAsync() 在异步任务产生结果时使用。它接受一个 Supplier 功能接口,并异步启动任务。随后,它会返回一个 CompletableFuture<T>,其中 T 是任务产生的结果的类型。
Let’s illustrate this with an example:
让我们举例说明一下:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Perform result-producing task
return "Result of the asynchronous computation";
});
// Get the result later
String result = future.get();
System.out.println("Result: " + result);
In this example, supplyAsync() is used to execute a result-producing task asynchronously. The lambda expression within supplyAsync() represents the task that computes the result asynchronously. Upon completion, it prints the obtained result:
在本例中,supplyAsync() 用于异步执行结果生成任务。supplyAsync() 中的 lambda 表达式表示异步计算结果的任务。完成后,它会打印得到的结果:
Result: Result of the asynchronous computation
4. Exception Handling
4.异常处理
In this section, we’ll discuss how both methods handle exceptions.
在本节中,我们将讨论这两种方法如何处理异常。
4.1. runAsync()
4.1 runAsync()
When using runAsync(), exception handling is straightforward. The method doesn’t provide an explicit mechanism for handling exceptions within the asynchronous task. As a result, any exceptions thrown during the execution of the task are propagated to the calling thread when invoking the get() method on the CompletableFuture. This means that we must handle exceptions manually after calling get().
使用 runAsync() 时,异常处理非常简单。该方法未提供在异步任务中处理异常的显式机制。因此,当调用 CompletableFuture 上的 get() 方法时,任务执行过程中抛出的任何异常都会传播到调用线程。这意味着我们必须在调用 get() 之后手动处理异常。
Here’s a snippet demonstrating exception handling with runAsync():
下面是使用 runAsync() 演示异常处理的片段:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
throw new RuntimeException("Exception occurred in asynchronous task");
});
try {
future.get(); // Exception will be thrown here
} catch (ExecutionException ex) {
Throwable cause = ex.getCause();
System.out.println("Exception caught: " + cause.getMessage());
}
The exception message is then printed:
然后打印异常信息:
Exception caught: Exception occurred in asynchronous task
4.2. supplyAsync()
4.2.supplyAsync()
In contrast, supplyAsync() provides a more convenient way to handle exceptions. It offers an exception-handling mechanism via the exceptionally() method. This method allows us to specify a function that will be invoked if the original asynchronous task completes exceptionally. We can use this function to handle the exception and return a default value or perform any necessary cleanup operations.
相比之下,supplyAsync() 提供了一种更方便的异常处理方式。它通过 exceptionally() 方法提供了一种异常处理机制。该方法允许我们指定一个函数,在原始异步任务异常完成时调用该函数。我们可以使用该函数处理异常,并返回默认值或执行任何必要的清理操作。
Let’s look at an example that demonstrates how exception handling works with supplyAsync():
下面我们来看一个示例,演示如何使用 supplyAsync() 进行异常处理:</em
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Exception occurred in asynchronous task");
}).exceptionally(ex -> {
// Exception handling logic
return "Default value";
});
String result = future.join(); // Get the result or default value
System.out.println("Result: " + result);
In this example, if an exception occurs during the execution of the asynchronous task, the exceptionally() method will be invoked. This allows us to handle the exception gracefully and provide a fallback value if needed.
在本例中,如果异步任务在执行过程中发生异常,将调用 exceptionally() 方法。这样,我们就可以从容地处理异常,并在需要时提供一个后备值。
Instead of raising an exception, it prints:
它不会引发异常,而是打印出来:
Task completed with result: Default value
5. Execution Behavior
5.执行行为
In this section, we’ll explore the execution behavior of CompletableFuture‘s runAsync() and supplyAsync() methods
在本节中,我们将探讨 CompletableFuture 的 runAsync() 和 supplyAsync() 方法的执行行为。
5.1. runAsync()
5.1 runAsync()
When utilizing runAsync(), the task is launched instantly in a common thread pool. Its behavior mirrors that of invoking a new Thread(runnable).start(). This means that the task begins execution immediately upon invocation, without any delay or scheduling considerations.
当使用 runAsync() 时,任务会在公共线程池中立即启动。其行为与调用新的 Thread(runnable).start() 类似。这意味着任务在调用后立即开始执行,而无需考虑任何延迟或调度因素。
5.2. supplyAsync()
5.2.supplyAsync()
On the other hand, supplyAsync() schedules the task in a common thread pool, potentially delaying its execution if other tasks are queued. This scheduling approach can be advantageous for resource management, as it helps prevent sudden bursts of thread creation. By queuing tasks and scheduling their execution based on the availability of threads, supplyAsync() ensures efficient resource utilization.
另一方面,supplyAsync() 在公共线程池中调度任务,如果其他任务正在排队,可能会延迟任务的执行。这种调度方法有利于资源管理,因为它有助于防止线程的突然创建。通过排队任务并根据线程的可用性调度任务的执行,supplyAsync() 确保了资源的有效利用。
6. Chaining Operations
6.连锁操作
In this section, we’ll explore how CompletableFuture‘s runAsync() and supplyAsync() methods support chaining operations, highlighting their differences.
在本节中,我们将探讨 CompletableFuture 的 runAsync() 和 supplyAsync() 方法如何支持链式操作,并重点说明它们之间的差异。
6.1. runAsync()
6.1 runAsync()
runAsync() method cannot be directly chained with methods like thenApply() or thenAccept() as it doesn’t produce a result. However, we can use thenRun() to execute another task after the runAsync() task completes. This method allows us to chain additional tasks for sequential execution without relying on the result of the initial task.
runAsync() 方法不能直接与 thenApply() 或 thenAccept() 等方法连锁,因为它不会产生结果。不过,我们可以使用 thenRun() 在 runAsync() 任务完成后执行另一个任务。该方法允许我们在不依赖初始任务结果的情况下,链式执行其他任务。
Below is an example showcasing the chaining operation using runAsync() and thenRun():
下面的示例展示了使用 runAsync() 和 thenRun() 进行的连锁操作:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Task executed asynchronously");
});
future.thenRun(() -> {
// Execute another task after the completion of runAsync()
System.out.println("Another task executed after runAsync() completes");
});
In this example, we first execute a task asynchronously using runAsync(). Then, we use thenRun() to specify another task to be executed after the completion of the initial task. This allows us to chain multiple tasks sequentially, resulting in the following output:
在本例中,我们首先使用 runAsync() 异步执行一个任务。然后,我们使用 thenRun() 指定在初始任务完成后执行另一个任务。这样,我们就可以按顺序链式执行多个任务,输出结果如下:
Task executed asynchronously
Another task executed after runAsync() completes
6.2. supplyAsync()
6.2.supplyAsync()
In contrast, supplyAsync() allows chaining operations due to its return value. Since supplyAsync() produces a result, we can use methods like thenApply() to transform the result, thenAccept() to consume the result, or thenCompose() to chain further asynchronous operations. This flexibility enables us to build complex asynchronous workflows by chaining multiple tasks together.
相比之下,supplyAsync() 由于其返回值而允许链式操作。由于 supplyAsync() 会产生一个结果,因此我们可以使用 thenApply() 等方法来转换结果,使用 thenAccept() 来消费结果,或使用 thenCompose() 来进一步连锁异步操作。这种灵活性使我们能够通过将多个任务连锁在一起来构建复杂的异步工作流。
Here’s an example illustrating the chaining operation with supplyAsync() and thenApply():
下面的示例说明了使用 supplyAsync() 和 thenApply() 的连锁操作:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Result of the asynchronous computation";
});
future.thenApply(result -> {
// Transform the result
return result.toUpperCase();
}).thenAccept(transformedResult -> {
// Consume the transformed result
System.out.println("Transformed Result: " + transformedResult);
});
In this example, we first execute a task asynchronously using supplyAsync(), which produces a result. Then, we use thenApply() to transform the result and thenAccept() to consume the transformed result. This demonstrates the chaining of multiple operations with supplyAsync(), allowing for more complex asynchronous workflows.
在这个示例中,我们首先使用 supplyAsync() 异步执行任务,产生结果。然后,我们使用 thenApply() 转换结果,并使用 thenAccept() 消费转换后的结果。这演示了使用 supplyAsync() 对多个操作进行连锁,从而实现更复杂的异步工作流。
Here’s an example of the output:
下面是一个输出示例:
Transformed Result: RESULT OF THE ASYNCHRONOUS COMPUTATION
7. Performance
7.性能
While both runAsync() and supplyAsync() execute tasks asynchronously, their performance characteristics can vary based on the nature of the tasks and the underlying execution environment.
虽然 runAsync() 和 supplyAsync() 都是异步执行任务,但它们的性能特性会根据任务的性质和底层执行环境而有所不同。
7.1. runAsync()
7.1. runAsync()
Since runAsync() doesn’t produce any result, it may have a slightly better performance compared to supplyAsync(). This is because it avoids the overhead of creating a Supplier object. The absence of result handling logic can lead to faster task execution in certain scenarios.
由于runAsync() 不会产生任何结果,因此与 supplyAsync() 相比,runAsync() 的性能可能略胜一筹。这是因为它避免了创建 Supplier 对象的开销。在某些场景中,没有结果处理逻辑可能会导致任务执行速度更快。
7.2. supplyAsync()
7.2.supplyAsync()
Various factors influence the performance of supplyAsync(), including the complexity of the task, the availability of resources, and the efficiency of the underlying execution environment.
影响 supplyAsync() 性能的因素有很多,包括任务的复杂性、资源的可用性和底层执行环境的效率。
In scenarios where tasks involve complex computations or resource-intensive operations, the performance impact of using supplyAsync() may be more pronounced. However, the ability to handle results and dependencies between tasks can outweigh any potential performance overhead.
在任务涉及复杂计算或资源密集型操作的场景中,使用 supplyAsync() 的性能影响可能会更加明显。不过,处理任务之间的结果和依赖关系的能力可能会超过任何潜在的性能开销。
8. Use Cases
8.用例
In this section, we’ll explore specific use cases for both methods.
在本节中,我们将探讨这两种方法的具体使用案例。
8.1. runAsync()
8.1. runAsync().
The runAsync() method is particularly useful when the focus is on the completion of a task rather than obtaining a specific result. runAsync() is commonly employed in scenarios where background tasks or operations that don’t require returning a value are performed. For example, running periodic cleanup routines, logging events, or triggering notifications can all be achieved efficiently with runAsync().
runAsync() 方法在需要完成任务而不是获取特定结果时特别有用。runAsync()通常用于执行不需要返回值的后台任务或操作的场景。例如,运行定期清理例程、记录事件或触发通知都可以通过 runAsync() 高效地实现。
8.2. supplyAsync()
8.2.supplyAsync()
In contrast to runAsync(), the supplyAsync() method is specifically useful when the completion of the task involves producing a value that may be utilized later in the application flow. One typical use case for supplyAsync() is fetching data from external sources, such as databases, APIs, or remote servers.
与runAsync()相反,supplyAsync()方法特别适用于任务完成后产生的值可能会在应用流程中稍后使用的情况。supplyAsync() 的一个典型用例是从外部来源获取数据,例如数据库、API 或远程服务器。
Additionally, supplyAsync() is suitable for executing computational tasks that generate a value as a result, such as performing complex calculations or processing input data.
此外,supplyAsync() 适用于执行结果会产生值的计算任务,例如执行复杂计算或处理输入数据。
9. Summary
9.摘要
Here’s a summary table comparing the key differences between runAsync() and supplyAsync():
下面的汇总表比较了 runAsync() 和 supplyAsync() 之间的主要区别:
Feature | runAsync() | supplyAsync() |
---|---|---|
Input | Accepts a Runnable representing a non-result task | Accepts a Supplier<T> representing a result-producing task |
Return Type | CompletableFuture<Void> | CompletableFuture<T> (where T is the result type) |
Use Case | Fire-and-forget tasks without result | Tasks requiring a result for further processing |
Exception Handling | No built-in mechanism; exceptions propagate to the caller | Provides exceptionally() for graceful exception handling |
Execution Behavior | Instantly launches task | Schedule tasks potentially delaying execution |
Chaining Operations | Supports thenRun() for subsequent tasks | Supports methods like thenApply() for chaining tasks |
Performance | May have a slightly better performance | Performance influenced by task complexity and resources |
Use Cases | Background tasks, periodic routines, notifications | Data fetching, computational tasks, result-dependent tasks |
10. Conclusion
10.结论
In this article, we explored the runAsync() and supplyAsync() methods. We discussed their functionalities, differences, exception-handling mechanisms, execution behavior, chaining operations, performance considerations, and specific use cases.
在本文中,我们探讨了 runAsync() 和 supplyAsync() 方法。我们讨论了它们的功能、差异、异常处理机制、执行行为、链式操作、性能注意事项和特定用例。
While supplyAsync() is preferable when a result is needed, runAsync() is suitable for scenarios where the focus is solely on task completion without requiring a specific result.
当需要结果时,supplyAsync() 更为可取,而runAsync() 则适用于只关注任务完成而不需要特定结果的场景。
As always, the source code for the examples is available over on GitHub.
与往常一样,这些示例的源代码可在 GitHub 上获取。