Not just how it’s used, but the thinking behind it. As for the title, it seems that the topic is somewhat irrelevant. I write articles coherently with each other, and the latter will use the former. This is a series:

  • First met
  • Get to know each other
  • Is happy
  • For a long time can place
  • Changle promise
  • Changle not ended

We’ll think about that when we run out.

preface

The new HttpClient in JDK11 supports HTTP/2 and WebSocket, so I tried to use it. Then I found the CompletableFuture class. I wrote the following code:

public static void main(String[] args) {
	  HttpClient httpClient = HttpClient.newBuilder().version(Version.HTTP_2).build(); 
	  httpClient.version();
	  CompletableFuture<WebSocket> result = httpClient.newWebSocketBuilder().buildAsync(null.null);
	  Class<? extends HttpClient> clazz = httpClient.getClass();	  
	}
Copy the code

Then I clicked on the source code for CompletableFuture, which has been introduced since 1.8 and is also about asynchronous programming. This class implements the Future and CompletionStage interfaces. Future we discussed in the previous article, so the CompletableFuture has the properties of a Future, and what is the CompletionStage? First of all, the class name CompletionStage is a compound word:

  • There’s a Completion.
  • -Dan: You’re on the Stage

So we can just think of the complete stage as either a completion step or a CompletionStage. Before we look at the CompletableFuture, let’s look at the CompletionStage. This article will cover the basics of Lambda expressions. If you are not familiar with Lambda expressions, see my article on Nuggets:

  • Lambda expression functional programming Stream API learning notes

Look at the CompletionStage

Look at the comments

A stage of a possibly asynchronous computation, that performs an action or computes a value when another CompletionStage completes. A stage completes upon termination of its computation, but this may in turn trigger other dependent stages. The functionality defined in this interface takes only a few basic forms, which expand out to a larger set of methods to capture a range of usage styles:

An asynchronous computed step may represent an action or another CompletionStage that computes the value at completion. A step ends when the calculation is complete, but this step may trigger other steps as well. The CompletionStage interface defines some basic forms of functions that can be extended to suit different styles.

  • The computation performed by a stage may be expressed as a Function, Consumer, or Runnable (using methods with names including apply, accept, or run, respectively) depending on whether it requires arguments and/or produces results. For example, stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println()). An additional form (compose) applies functions of stages themselves, rather than their results.

This class of steps is represented as Function(Lambda expression, functional interface), Consumer(Lambda expression, functional interface), Runnable(method names include apply, Accept, run) examples:

Stage.thenapply (x -> square(x)).thenAccept(x -> system.out.print (x)).thenrun (() -> system.out.println ()).thenrun () -> system.out.println () ThenApply takes the value of the receive method, passes it to thenAccept, and then thenRun.Copy the code

Another use of the form is to apply functions of the steps themselves rather than their results.

  • One stage’s execution may be triggered by completion of a single stage, or both of two stages, or either of two stages. Dependencies on a single stage are arranged using methods with prefix then. Those triggered by completion of both of two stages may combine their results or effects, using correspondingly named methods. Those triggered by either of two stages make no guarantees about which of the results or effects are used for the dependent stage’s computation.

The execution of one step may be triggered by the completion of the other step or by the completion of either step when both steps are completed. Methods prefixed with then that depend on a step. Methods that are triggered by the completion of two steps and require a result can use method names that contain Combine. A step that can be triggered when Either step is completed. The result received by the next step is not guaranteed. You can use the method name treasure line Either.

  • Dependencies among stages control the triggering of computations, but do not otherwise guarantee any particular ordering. Additionally, execution of a new stage’s computations may be arranged in any of three ways: default execution, default asynchronous execution (using methods with suffix async that employ the stage’s default asynchronous execution facility), or custom (via a supplied Executor). The execution properties of default and async modes are specified by CompletionStage implementations, not this interface. Methods with explicit Executor arguments may have arbitrary execution properties, and might not even support concurrent execution, but are arranged for processing in a way that accommodates asynchrony.

If the computation and firing between steps does not require a specific order, asynchronous execution can be considered. There are three forms of step execution: default execution, asynchronous execution (async method name, which is asynchronous execution at the time of execution), and custom (support for receiving executor). The execution properties of the default and asynchronous modes are specified by the CompletionStage implementation, not in this interface. Methods with explicit Executor parameters may have arbitrary execution properties, and may not even support concurrent execution, but may be handled in a manner that can accommodate async.

  • Two method forms support processing whether the triggering stage completed normally or exceptionally: Method whenComplete allows injection of an action regardless of outcome, otherwise preserving the outcome in its completion. Method handle additionally allows the stage to compute a replacement result that may enable further processing by other dependent stages. In all other cases, if a stage’s computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. If a stage is dependent on both of two stages, and both complete exceptionally, then the CompletionException may correspond to either one of these exceptions. If a stage is dependent on either of two others, and only one of them completes exceptionally, no guarantees are made about whether the dependent stage completes normally or exceptionally. In the case of method whenComplete, when the supplied action itself encounters an exception, then the stage exceptionally completes with this exception if not already completed exceptionally.

There are two methods for triggering normal completion or abnormal completion. When executed, the whenComplete method ignores whether it finishes normally or abnormally. If it finishes abnormally, whenComplete receives null and the exception has a value. The Handle method computes the result of the previous step and lets the next stage take over. Again, if the other steps end because of exceptions and errors, all steps that rely on this step will be completed (the completeExceptionally method), and completeExceptionally holds the reason for the exception’s completion. If a step depends on one of two steps, there is no guarantee that the next step will be completed normally or abnormally if the two dependent steps end unexpectedly. If an exception occurs in the action received by whenComplete, the next phase also carries the exception that occurred in the whenComplete method.

My experience

If a certain task is relatively large, we will usually break these tasks down and divide the labor. It is quite common in real life to move on to the next step after some steps have been completed. What about the software world, what about in Java, where we need to delegate threads to work? The working model looks like this:What are the interfaces we can use? Consider the previous thread collaboration:

  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • Future
  • FutureTask

All of this might seem like a bit of a chore to put together to accomplish the goal that we’re proposing, and the CompletionStage is designed to solve computational tasks like this, breaking down tasks into steps, and returning to the CompletionStage when they’re done. Note the task model mentioned:

  • One task after another (after one task is triggered to the next) method names include apply, Accept, then, and run for this task model
  • When multiple tasks are completed and the next task is performed, combine is included in the method name for this task model
  • The method name with Either in it corresponds to a task model that triggers the next task when one of the tasks completes

Async is asynchronous execution where a thread is started to perform the task. The callbacks triggered by method normal completion and exception completion are whenComplete and Handle. But the CompletionStage only provides the specification, and to actually use it we have to look at its implementation class CompletableFuture.

CompletableFuture simple use example

Example of single-chain tasks

   /** * Cooking task example ** First buy vegetables, cut vegetables, then eat */
    private static void makeFoodDemo(a) {
        CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("Go grocery shopping.");
            return "Success in buying vegetables";
        }).thenApplyAsync(o -> {
            System.out.println("Start cooking.");
            return "Cooking is done.";
        });
        // This method is triggered when the task completes
        System.out.println(task.join()+"Start eating.");
    }
Copy the code

The sample is triggered only if both are successful

   /** * Merge result */
    private static void appointmentTaskDemo(a) {
        CompletableFuture<String> boyTask = CompletableFuture.supplyAsync(() -> {
            return "The boy sends an invitation.";
        });

        boyTask.thenCombineAsync(CompletableFuture.supplyAsync(()->"Girls accept invitations."),(o1,o2)->{
            System.out.println(o1);
            System.out.println(o2);
            return "Start dating";
        }).thenAccept(o-> System.out.println(o));
    }
Copy the code

The sample is triggered only if either of the two are successful

 /** * Cooking task example ** First buy vegetables, cut vegetables, then eat */
    private static void makeFoodDemo(a) {
        CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("Go grocery shopping.");
            return "Success in buying vegetables";
        }).thenApplyAsync(o -> {
            System.out.println("Start cooking.");
            return "Cooking is done.";
        });
        // This method is triggered when the task completes
        System.out.println(task.join()+"Start eating.");
    }
Copy the code

conclusion

The CompletableFuture cutting task is smooth enough. So that’s how I learned, you know, if there’s a comment, you read it first, and in most cases, it’s very simple.