Elegant concurrent programming-CompletableFuture

Directory

Learn about CompletableFuture

CompletableFuture is a class introduced in Java 8 to support asynchronous programming and non-blocking operations. It provides a simple and powerful way to handle asynchronous tasks, can easily implement parallel, non-blocking operations, and provides rich methods to handle the completion status of tasks, exceptions, and concatenation and concatenation between multiple tasks. combination.

CompletableFuture usage scenarios

  1. Process multiple independent tasks in parallel: When a task can be decomposed into multiple independent subtasks, CompletableFuture can be used to execute these subtasks in parallel, thereby improving the performance and response speed of the system. For example, in an e-commerce system, querying user information, order information, shopping cart information, etc. can be executed in parallel, and then the results are merged after all subtasks are completed.
  2. Asynchronous execution of time-consuming operations: For some time-consuming operations, such as remote calls, database queries, etc., you can use CompletableFuture to asynchronously execute these operations to avoid blocking the main thread and improve the system’s throughput and concurrency capabilities.
  3. Combining the results of multiple asynchronous tasks: Sometimes you need to wait for multiple asynchronous tasks to complete before proceeding to the next step. You can use the combination method of CompletableFuture (such as thenCombine, thenCompose, etc.) to wait for the results of multiple asynchronous tasks and perform the next step on all of them. Process the task after it is completed.
  4. Timeout handling and exception handling: CompletableFuture provides a wealth of exception handling and timeout handling methods, which can easily handle exceptions or timeouts that occur during the execution of asynchronous tasks.
  5. Implement asynchronous callback: Through the callback method of CompletableFuture, you can execute specific logic after the asynchronous task is completed, such as notifying other systems, recording logs, etc.

pexels-umut-sar?alan-18038272.jpg

Multi-threaded task use cases

I will use the following API of CompletableFuture to illustrate the use of CompletableFuture.

  • supplyAsync: Starts an asynchronous task and returns a CompletableFuture object, which will be executed in the incoming Supplier. In this example, the first CompletableFuture starts an asynchronous task, simulates a time-consuming operation, and returns an integer result.
  • exceptionally: Handle exceptions that occurred during the execution of the previous stage and return a default value. The result2 here uses the exceptionally method to handle exceptions in the result1 stage. If an exception occurs in result1, a string containing the exception information is returned.
  • thenApply: Process the results of the previous stage and return a new CompletableFuture object. In this example, result1 is obtained by processing the result of future1, converting the result of asynchronous task 1 to a string and adding additional processing.
  • exceptionally: Handle exceptions that occurred during the execution of the previous stage and return a default value. The result2 here uses the exceptionally method to handle exceptions in the result1 stage. If an exception occurs in result1, a string containing the exception information is returned.
  • orTimeout: Set the timeout for asynchronous tasks. In this example, result3 uses the orTimeout method to set the timeout of the task to 2 minutes. If the task is not completed within the specified time, a TimeoutException will be thrown.
  • runAsync: Starts an asynchronous task but does not return any results. In this example, result4 represents an asynchronous task that does not return a value and simply prints a message.
  • allOf: Wait for all CompletableFutures to be executed. In this example, by using the allOf method, wait for result2, result3, and result4 to complete before continuing to execute subsequent logic.
  • join: Wait for the completion of CompletableFuture and get the result. In this example, we wait for all tasks to complete and get their results by calling the join method.

API use cases

public class CompletableFutureExample {<!-- -->

    public static void main(String[] args) throws ExecutionException, InterruptedException {<!-- -->
        //Asynchronous task startup: supplyAsync
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {<!-- -->
            System.out.println("Asynchronous task 1 starts execution");
            try {<!-- -->
                //Simulate time-consuming operations
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {<!-- -->
                throw new IllegalStateException(e);
            }
            return 100;
        });

        //Asynchronous task result processing: thenApply
        CompletableFuture<String> result1 = future1.thenApply(value -> {<!-- -->
            System.out.println("Task 1 result processing: " + value);
            return "Processed Result1: " + value;
        });

        //Concatenate/combine the processing results of multiple tasks: exceptionally
        CompletableFuture<String> result2 = result1.exceptionally(ex -> "An exception occurred in task 1: " + ex.getMessage());

        // Timeout processing: orTimeout
        CompletableFuture<String> result3 = CompletableFuture.supplyAsync(() -> {<!-- -->
            try {<!-- -->
                //Simulate time-consuming operations
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {<!-- -->
                throw new IllegalStateException(e);
            }
            return "Task 2 completed";
        }).orTimeout(2, TimeUnit.MINUTES);

        // Stream processing/functional programming: combined with the Stream interface
        CompletableFuture<Void> result4 = CompletableFuture.runAsync(() -> {<!-- -->
            System.out.println("Task 3 begins execution");
        });

        // Wait for all tasks to complete
        CompletableFuture.allOf(result2, result3, result4).join();

        System.out.println(result2.get());
        System.out.println(result3.get());
    }
}

Business optimization case

Suppose you are a senior engineer and your project team is engaged in e-commerce-related business and has high requirements for the response speed of the interface. Now there is a need to query the user’s account information, order information, and shopping cart information at the same time. , bank card payment information, if there is no concurrency thought, then the line of sight logic of the interface is roughly as follows:

public class ECommerceService {<!-- -->
    public UserAccount getUserAccount() {<!-- -->
        //Execute the logic of querying user account information
        UserAccount userAccount = // ...;
        return userAccount;
    }

    public OrderInfo getOrderInfo() {<!-- -->
        // Execute the logic of querying order information
        OrderInfo orderInfo = // ...;
        return orderInfo;
    }

    public ShoppingCart getShoppingCart() {<!-- -->
        // Execute the logic of querying shopping cart information
        ShoppingCart shoppingCart = // ...;
        return shoppingCart;
    }

    public BankCardPaymentInfo getBankCardPaymentInfo() {<!-- -->
        // Execute the logic of querying bank card payment information
        BankCardPaymentInfo bankCardPaymentInfo = // ...;
        return bankCardPaymentInfo;
    }

    public void processECommerceRequest() {<!-- -->
        UserAccount userAccount = getUserAccount();
        OrderInfo orderInfo = getOrderInfo();
        ShoppingCart shoppingCart = getShoppingCart();
        BankCardPaymentInfo bankCardPaymentInfo = getBankCardPaymentInfo();

        // Merge and process the query results here
        // ...
    }
}

The above is the logic of serial execution of the interface. The current server environment is generally multi-core and multi-threaded. The server can handle multiple tasks at the same time. If a single thread is used for execution at this time, it will be a waste of resources and the interface response speed will be relatively low. Slow, then the optimization method is to open multiple threads to execute different logic respectively, then we can use CompletableFuture to optimize the code and improve the interface response speed.

import java.util.concurrent.CompletableFuture;

public class ECommerceService {<!-- -->
    public CompletableFuture<UserAccount> getUserAccountAsync() {<!-- -->
        return CompletableFuture.supplyAsync(() -> {<!-- -->
            //Execute the logic of querying user account information
            UserAccount userAccount = // ...;
            return userAccount;
        });
    }

    public CompletableFuture<OrderInfo> getOrderInfoAsync() {<!-- -->
        return CompletableFuture.supplyAsync(() -> {<!-- -->
            // Execute the logic of querying order information
            OrderInfo orderInfo = // ...;
            return orderInfo;
        });
    }

    public CompletableFuture<ShoppingCart> getShoppingCartAsync() {<!-- -->
        return CompletableFuture.supplyAsync(() -> {<!-- -->
            // Execute the logic of querying shopping cart information
            ShoppingCart shoppingCart = // ...;
            return shoppingCart;
        });
    }

    public CompletableFuture<BankCardPaymentInfo> getBankCardPaymentInfoAsync() {<!-- -->
        return CompletableFuture.supplyAsync(() -> {<!-- -->
            // Execute the logic of querying bank card payment information
            BankCardPaymentInfo bankCardPaymentInfo = // ...;
            return bankCardPaymentInfo;
        });
    }

    public CompletableFuture<Void> processECommerceRequest() {<!-- -->
        CompletableFuture<UserAccount> userAccountFuture = getUserAccountAsync();
        CompletableFuture<OrderInfo> orderInfoFuture = getOrderInfoAsync();
        CompletableFuture<ShoppingCart> shoppingCartFuture = getShoppingCartAsync();
        CompletableFuture<BankCardPaymentInfo> bankCardPaymentInfoFuture = getBankCardPaymentInfoAsync();

        return CompletableFuture.allOf(userAccountFuture, orderInfoFuture, shoppingCartFuture, bankCardPaymentInfoFuture)
            .thenAcceptAsync((Void) -> {<!-- -->
                UserAccount userAccount = userAccountFuture.join();
                OrderInfo orderInfo = orderInfoFuture.join();
                ShoppingCart shoppingCart = shoppingCartFuture.join();
                BankCardPaymentInfo bankCardPaymentInfo = bankCardPaymentInfoFuture.join();

                // Combine the results of processing various asynchronous tasks here
                // ...
            });
    }
}

The above is that the account, order, shopping cart, and bank card payment logic blocks each start a thread to execute asynchronously, and finally wait for each thread to complete the execution and summarize the results to improve the interface impact speed.

Compared with single-threading and multi-threading, here is an inappropriate example: When you want to take revenge, you go up the mountain with great ambition and knock on the door of your teacher. After ten years of hard training and practicing day and night, you finally come back and go down the mountain to take revenge. As a result, the enemy comes up with AK 47 suddenly bursts out, and the feeling of powerlessness tells you: “Sir, times have changed”.

A little tip

Question

When CompletableFuture is executed in the thread pool, code exceptions may occur, but the exceptions are not thrown. There are two reasons:

  1. In the asynchronous task of CompletableFuture, if an exception occurs but is not explicitly handled or thrown, the exception will be swallowed and will not be propagated to the final result of CompletableFuture superior.
  2. Another possibility is that exceptions occurred during the execution of the asynchronous task, but these exceptions were not handled correctly during subsequent processing of CompletableFuture. For example, when using methods such as thenApply and exceptionally to process the results of CompletableFuture, possible exceptions are not considered, causing the exception to be blocked. cover.

Solution

To solve this problem, you can handle exceptions appropriately and throw them in the code of the asynchronous task, or use the exceptionally method to handle exceptions to ensure that exceptions can be propagated and handled correctly. In addition, when processing the results of CompletableFuture, you need to pay attention to handling possible exceptions to ensure that exceptions can be caught and handled in time.

Analysis of advantages and disadvantages of CompletableFuture

CompletableFuture is a tool class introduced in Java 8 to support asynchronous programming. It provides a rich API to simplify asynchronous programming and provides the combination, serialization and parallelization of multiple asynchronous operations. Implementation support. The following is an analysis of some advantages and disadvantages of CompletableFuture:

Advantages:

  1. Simplify asynchronous programming: CompletableFuture provides a concise API, making asynchronous programming more intuitive and easy to understand. It allows developers to process, transform, and even stream the results of asynchronous tasks at different stages in a functional programming manner.
  2. Asynchronous task combination: CompletableFuture supports the combination, serial and parallel execution of multiple asynchronous tasks, which can be done through thenCompose, thenCombine code> and other methods to flexibly organize complex asynchronous task processes.
  3. Exception handling: CompletableFuture provides an exception handling mechanism, which can handle exceptions during the execution of asynchronous tasks through the exceptionally method to ensure that exceptions can are captured and processed correctly.
  4. Timeout processing: CompletableFuture supports setting the timeout for asynchronous tasks. You can specify the timeout through the orTimeout method to prevent tasks from being blocked due to long execution.
  5. Combined with functional programming: CompletableFuture uses the idea of functional programming to make the processing of asynchronous tasks more flexible, and can be used in conjunction with other functional programming tools such as the Stream interface. .

Disadvantages:

  1. Learning Curve: For beginners, CompletableFuture’s API may have a certain learning curve, especially for developers who are not familiar with functional programming and asynchronous programming models.
  2. Excessive use of complexity: In some simple scenarios, using CompletableFuture may appear to be too complex, especially in some simple linear task processing, which may appear cumbersome. .
  3. Debugging difficulties: Since CompletableFuture supports the combination of asynchronous tasks and serial/parallel execution, when a logic error or exception occurs, you may need to carefully trace CompletableFuture each link in the chain to determine where the problem lies, which can make debugging more difficult.

To sum up, CompletableFuture, as a powerful tool for Java asynchronous programming, provides rich functions and flexible operation methods. However, when using it, you need to weigh its advantages and disadvantages according to the actual situation to avoid overly complicating simple tasks. Task processing, and reasonable handling of exceptions and ensuring code readability and maintainability.

Regarding the implementation principles of CompletableFuture, please leave a message and we will update it tomorrow.

Thank you for reading to the end. I hope it can solve your troubles and doubts. This is my biggest motivation for updating.

About me

Hello, I am Debug.c. WeChat public account: The maintainer of Zhongke Code Technology Tree is a cross-professional self-taught Java bugger with a passion for technology. He is also a Java Coder who has worked hard in a second-tier city for more than four years.

In the Nuggets, CSDN, and public accounts, I will share what I have learned recently, the pitfalls I have encountered, and my understanding of technology.

If you are interested in me, please contact me.

If you find anything useful, please give it a like