[JAVA] Detailed CompletableFuture

tags: java  Concurrent programming  Multithreading  spring  Gateway  

   

table of Contents

CompletionStage

CompletableFuture

CompletableFuture delivery chain implementation

Completion

Key source code (take thenApply as an example)

complete/completeExceptionally

obtrudeValue/obtrudeExceptionlly

AnyOf and allof

Small chestnuts


Recently, a cooperative proxy gateway has been developed due to the needs of the project. Its main functions are addition and de-signature, input parameter conversion, routing forwarding (supporting http/dubbo protocol forwarding), and output parameter conversion. The first version launched a blocking version (every request from receiving to forwarding to the return thread is always occupied), the gateway will generally hang many business systems, a slow response will drag down the entire gateway, so we implemented non-blocking Upgrade. In non-blocking upgrades, the bottom layer of dubbo originally supports asynchronous calls-CompletableFuture. For a long time, I didn't think CompletableFuture has any practical effect, until this time I found it is so wonderful. The CompletableFuture mechanism can help us build a chain. When a request comes, such a chain is constructed from the starting point, and when there is a response, the response is pushed to the starting point from the end.

CompletionStage

   


This delivery chain is very simple and just a path. The end stage is triggered after a result is obtained, and the result is delivered to B. B is delivering result A, and the final result reaches the starting point. The results may change in each Stage during the intermediate transfer process (depending on the logic of each Stage). To

This is a relatively complicated link. The completion of C is triggered by end point 1 alone. After C is completed, A is triggered, and A pushes the result to start point 1 and start point 2. When B is completed, both stages C and end 2 must have results. , C and end point 2 will push their respective results to B, and B will push the result to end point 3 after processing.

CompletionStage can construct a more complex delivery chain. Using this mechanism, we can make the system non-blocking, especially suitable for gateway systems. Just imagine that the gateway receives the request and constructs a result delivery chain. After forwarding the request, it immediately releases the thread, monitors the socket data return, and assembles the socket after the data returns. As a result, began to push the results along the transmission chain to the starting point. The starting point writes the result to the front-end socket and returns the data. The gateway is not blocked by the request during the whole process. ...

CompletionStage provides other methods to construct the chain of result delivery, and distinguishes them by method name: apply, accept, run, handle, etc. Async on the method name indicates that the action is executed asynchronously (the thread pool can be passed, or the default thread pool can be used).

method Description Triggering conditions
<U> CompletionStage<U>thenAppy(Function<? super T,? extends U> fn)/thenApplyAsyn

The function execution is triggered after the current stage ends normally. The result of the current stage is the parameter of the Function, and a new CompletionStage is returned. The result of the new CompletionStage is the result of the Function or an exception

The current stage is completed, and the action is executed only when it ends normally
CompletionStage<Void> thenAccept(Consumer<? super T> action)/thenAcceptAsync
Consumer execution is triggered after the current Stage ends normally. The result of the current Stage is the parameter of Consumer, and a new CompletionStage is returned. The result of the new CompletionStage is java.util.concurrent.CompletableFuture#NIL(null) or abnormal The current stage is completed, and the action is executed only when it ends normally
CompletionStage<Void> thenRun(Runnable action)/thenRunAsync
Runnable execution is triggered after the current Stage ends normally,Runnable has no parameters (different from thenAccept), And return a new CompletionStage, the result of the new CompletionStage is NIL (null) or abnormal The current stage is completed, and the action is executed only when it ends normally
<U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)/thenComobineAsync
After the current Stage and "other" Stage are finished normally, the execution of BiFunction is triggered. The results of the current Stage and "other" Stage are used as the parameters of BiFunction, and a new CompletionStage is returned. The result of the new CompletionStage is the result or exception of BiFunction The current stage and the "other" stage are completed, and the action is executed only when it ends normally
<U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action)/thenAcceptBothAsync
After the current Stage and "other" Stage are finished normally, the execution of BiConsumer is triggered. The results of the current Stage and "other" Stage are used as the parameters of BiConsumer, and a new CompletionStage is returned. The new CompletionStage is NIL (null) or abnormal The current stage and the "other" stage are completed, and the action is executed only when the stage ends normally
CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action)/runAfterBothAsync
Runnalbe execution is triggered after the current Stage and "other" Stage are finished normally,Runnable has no parameters, And return a new CompletionStage, the data object type of the new CompletionStage is Void The current stage and the "other" stage are completed, and the action is executed only when the stage ends normally
<U> CompletionStage<U> applyToEither (CompletionStage<? extends T> other, Function<? super T, U> fn)/applyToEitherAsync
Function execution is triggered after the current Stage or "other" Stage ends normally, the result of the current Stage or "other" Stage is used as the parameter of Function (so the type of "other" Stage must be a subclass of the current Stage), and a new CompletionStage is returned , The data object type of the new CompletionStage can be changed by Fucntion The current Stage or "other" Stage is completed, and the action is executed only when it ends normally
CompletionStage<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)/acceptEitherAsync
Consumer execution is triggered after the current Stage or "other" Stage ends normally, the result of the current Stage or "other" Stage is used as the parameter of Consumer (so the type of "other" Stage must be a subclass of the current Stage), and a new CompletionStage is returned , The result of the new CompletionStage is NIL (null) or abnormal The current Stage or "other" Stage is completed, and the action is executed only when it ends normally
CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action)/runAfterEitherAsyn
Runnable execution is triggered after the current Stage or "other" Stage ends normally. The result of the current Stage or "other" Stage is used as the parameter of Runnable (so the type of "other" Stage is irrelevant), and a new CompletionStage is returned. The result of the new CompletionStage is NIL (null) or exception The current Stage or "other" Stage is completed, and the action is executed only when it ends normally
<U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)/thenComposeAsyn
Function execution is triggered after the current stage ends normally, the current stage result is used as the parameter of the function, and a new CompletionStage (called the final stage) is returned. What is different from before is that Function returns a CompletionStage (Intermediate Stage). Only the completion of the Intermediate Stage will make the final Stage complete. The result of the final Stage is the result or exception of the Intermediate Stage.

2 stage trigger:

The first stage: the current stage is completed, only the normal termination triggers the execution of Function and returns to the intermediate stage

The second stage: the completion of the intermediate stage triggers the completion of the final stage

CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn)
Function execution is triggered after the current Stage ends abnormally. The exception of the current Stage is the parameter of the Function, and a new CompletionStage is returned. The result of the new CompletionStage is the result of the Function or the result of the current Stage (normally ended) or an exception (thrown by Functioin) The current Stage is completed, and the action is executed only when it ends abnormally
public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)/whenCompleteAsync
After the current Stage ends (normal/abnormal), the execution of BiConsumer is triggered. The result/excption of the current Stage is the parameter of BiConsumer, and a new CompletionStage is returned. The result and exception of the new CompletionStage are the result and exception of the current Stage. When the current stage is completed, the action will be executed when it ends normally or abnormally
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)/handleAsync
After the current Stage ends (normal/abnormal), the execution of BiFunction is triggered. The result/excption of the current Stage is the parameter of BiConsumer, and a new CompletionStage is returned. The result of BiFunction is the result of the new Stage. The difference from whenComplete is that the handle can be adjusted through BiFunction. abnormal When the current stage is completed, the action will be executed when it ends normally or abnormally

 

CompletableFuture

CompletionStage is an interface that defines a standard mechanism, but has not been implemented, nor has it been implemented to construct a delivery chain. CompletableFuture does these things and implements the interface specification of CompletionStage.

CompletableFuture delivery chain implementation

Every CompletableFuture object has a java.util.concurrent.CompletableFuture.Completion linked list, and the Completion object wraps the destination CompletableFuture object. When a CompletableFuture object completes and triggers the Completion object in the Completion chain in turn, the Completion object determines whether the condition is met (it may depend on multiple CompletableFutures), executes the action if it is satisfied, and assigns the result to its own packaging destination CompletableFuture object, triggering the destination CompletableFuture object carry out. Purpose CompletableFuture object will traverse its own Completion chain after completion.

Completion

abstract static class Completion extends ForkJoinTask<Void>
    implements Runnable, AsynchronousCompletionTask {
    volatile Completion next;      // Treiber stack link
    
    /**
     * Performs completion action if triggered, returning a
     * dependent that may need propagation, if one exists.
     *
     * @param mode SYNC, ASYNC, or NESTED
     */
    abstract CompletableFuture<?> tryFire(int mode);
    
    /** Returns true if possibly still triggerable. Used by cleanStack. */
    abstract boolean isLive();
    
    public final void run()                { tryFire(ASYNC); }
    public final boolean exec()            { tryFire(ASYNC); return true; }
}

next: Point to the next Completion to form a Completion chain. Note that a Completion chain directly depends on the current CompletableFuture. Each Completion object wraps the purpose CompletableFuture, and it also has its own Completion chain.

  • tryFire: Try to determine whether Completion meets the conditions, perform related actions when satisfied, and pass the result to the destination CompletableFuture, tryFire supports 3 modes
    • NESTED(-1): Nested mode-external conditions cause the CompletableFuture object to complete (complete/completeExceptionally/obtrudeValue/obtrudeExceptionlly). These methods will call the postComplete method, which triggers the execution of the Completion chain of the current CompletableFuture in nested mode, nesting The characteristic of the mode is that when a Completion meets the conditions and gives the destination CompletableFuture result, it will not call the postComplete of the execution destination CompletableFuture, but hang the Completion chain of the destination CompletableFuture on the Completion chain of the current object.

    • ASYNC(1): Asynchronous mode-if the current Completion is an asynchronous Completion (there is a thread pool, the Async method of CompletionStage is called when the delivery chain is constructed), then when the Completion condition is met, the Completion will be thrown to the thread pool scheduling After the thread pool is scheduled, tryFire is called in ASYNC mode. The feature of the asynchronous mode is that it is not allowed to claim sovereignty over Completion (java.util.concurrent.CompletableFuture.UniCompletion#claim) when performing actions, because it is throwing Completion to the thread. The pool has previously claimed (java.util.concurrent.CompletableFuture.UniCompletion#claim) once to ensure that a Completion will only be thrown to the thread pool once
    • SYNC(0): Synchronous mode-when the method of CompletionStage is called to construct the delivery chain, there is a concurrency problem, and it is proactively tried to meet the conditions. The feature of the synchronous mode is that it has no features. It must execute the postComplete of the target CompletableFuture and claim sovereignty.
  • isLive: Whether Completion is active, the judgment condition is generally whether the purpose CompletableFuture is empty (set to empty after the action is executed), if it is not active, it will be removed from the Completion chain
  • run/exec: both execute tryFire in asynchronous mode

Key source code (take thenApply as an example)

public <U> CompletableFuture<U> thenApply(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }

The first is to create a new CompletableFuture, try to execute uniApply (because if the current object is completed, you can directly execute the action and set the result of the new object), if the execution result is false (that is, the current object is not completed), create UniApply( Completion subclass) and push to the Completion chain of the current object, and finally try again whether Completion can be executed. If it is asynchronous, directly create Completion and hang it on the chain.

private <V> CompletableFuture<V> uniApplyStage(
        Executor e, Function<? super T,? extends V> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<V> d =  new CompletableFuture<V>();
        if (e != null || !d.uniApply(this, f, null)) {
            UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
            push(c);
            c.tryFire(SYNC);
        }
        return d;
    }

The key part of the uniApply method is to declare sovereignty before executing the action (the Async mode parameter c is empty, and there is no need to claim sovereignty), if it has been declared by another thread, it will return directly and abandon the subsequent execution (because other threads are triggering ), if it is claimed to be sovereign but asynchronous Completion is also returned directly, and the Completion is submitted to the thread pool (the submission thread pool will only be submitted once after the claim is made,Claiming sovereignty is actually a cas mechanism lock)。

final <S> boolean uniApply(CompletableFuture<S> a,
                               Function<? super S,? extends T> f,
                               UniApply<S,T> c) {
        Object r; Throwable x;
        if (a == null || (r = a.result) == null || f == null)
            return false;
        tryComplete: if (result == null) {
            if (r instanceof AltResult) {
                if ((x = ((AltResult)r).ex) != null) {
                    completeThrowable(x, r);
                    break tryComplete;
                }
                r = null;
            }
            try {
                                 //Author’s note: If it is executed in Sync or Nested mode, c is not empty, and sovereignty needs to be claimed to ensure that only one thread executes the current Completion.
                               //If it comes in Async mode, c is empty, because Async can only be triggered by Sync or Nested when it claims sovereignty, that is, Async has previously claimed sovereignty and has ensured that the thread pool will only be scheduled once.
                if (c != null && !c.claim())
                    return false;
                @SuppressWarnings("unchecked") S s = (S) r;
                completeValue(f.apply(s));
            } catch (Throwable ex) {
                completeThrowable(ex);
            }
        }
        return true;
    }

UniApply’s tryFire is the uniApply of the execution purpose CompletableFuture, and the postFire of the purpose CompletableFuture is triggered when the conditions are met.

static final class UniApply<T,V> extends UniCompletion<T,V> {
        Function<? super T,? extends V> fn;
        UniApply(Executor executor, CompletableFuture<V> dep,
                 CompletableFuture<T> src,
                 Function<? super T,? extends V> fn) {
            super(executor, dep, src); this.fn = fn;
        }
        final CompletableFuture<V> tryFire(int mode) {
            CompletableFuture<V> d; CompletableFuture<T> a;
            if ((d = dep) == null ||
                !d.uniApply(a = src, fn, mode > 0 ? null : this))
                return null;
            dep = null; src = null; fn = null;
            return d.postFire(a, mode);
        }
    }

The postFire method is mainly to clean up the Completion chain of the source CompletableFuture and remove the completed Completion (judging by isLive). Finally, if it is in Nested mode, you only need to return the destination CompletableFuture, because the postComplete method will hang the CompletableFuture of the destination CompletableFuture on the current chain; if it is in other modes, you need to trigger the postComplete of the destination CompletableFuture.

final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
        if (a != null && a.stack != null) {
            if (mode < 0 || a.result == null)
                a.cleanStack();
            //Author's note: I feel redundant here, because a is the source CompletableFuture. No matter how the a is set and completed, the Completion chain on it will be triggered. Why is the purpose of a CompletableFuture to be triggered, I don’t understand
            else 
                a.postComplete();
        }
        if (result != null && stack != null) {
            if (mode < 0)
                return this;
            else
                postComplete();
        }
        return null;
    }

complete/completeExceptionally

You can see that these two methods are to assign results to CompletableFuture, and then call postComplete to trigger the execution of the Completion chain. The result is given using the CAS mechanism

public boolean complete(T value) {
        boolean triggered = completeValue(value);
        postComplete();
        return triggered;
    }

    /**
     * If not already completed, causes invocations of {@link #get()}
     * and related methods to throw the given exception.
     *
     * @param ex the exception
     * @return {@code true} if this invocation caused this CompletableFuture
     * to transition to a completed state, else {@code false}
     */
    public boolean completeExceptionally(Throwable ex) {
        if (ex == null) throw new NullPointerException();
        boolean triggered = internalComplete(new AltResult(ex));
        postComplete();
        return triggered;
    }

    final boolean internalComplete(Object r) { // CAS from null to r
        return UNSAFE.compareAndSwapObject(this, RESULT, null, r);
    }

final boolean completeValue(T t) {
        return UNSAFE.compareAndSwapObject(this, RESULT, null,
                                           (t == null) ? NIL : t);
    }

 

obtrudeValue/obtrudeExceptionlly

The difference between these two methods and complete/completeExceptionlly is that they are relatively rude, regardless of whether the current result has a value, it is directly overwritten.

public void obtrudeValue(T value) {
        result = (value == null) ? NIL : value;
        postComplete();
    }

    
    public void obtrudeException(Throwable ex) {
        if (ex == null) throw new NullPointerException();
        result = new AltResult(ex);
        postComplete();
    }

anyOf and allof

        CompletableFuture also provides two static methods anyof and allOf. The parameters are all CompletableFuture arrays, and both methods return a new CompletableFuture. The difference is that anyOf only has a CompletableFuture object A in the parameter array is completed, then the new object returned by anyOf is completed and the result of the new object is the result of that object A; and allOf requires all CompletableFuture in the array to be completed, and the new object is completed, and new The result of the subject is NIL or abnormal. The implementation mechanism of anyOf and allOf is an inverted binary tree.

Small chestnuts

There is no time to write, so let's add later. I'm about to vomit blood. . . Go home to hug your daughter

 

Intelligent Recommendation

Java "concurrent programming" Future mode (with detailed explanation of CompletableFuture)

Stephen Zhu CPlusPlus 2019-05-23 09:13:00 Future Future is a Java5 added class that describes the result of an asynchronous calculation. You can use the isDone method to check if the calculation is co...

Asynchronous programming workers: completablefuture Detailed | Java development actual combat

Foreword When we perform a task asynchronously, we are generally created with thread pool Executor. If there is no need to have a return value, the task implements the runnable interface; if you need ...

Asynchronous programming weapon: CompletableFuture detailed explanation | Java development actual combat

When we perform a task asynchronous, we are generally created with thread pool Executor. If you do not need to have a return value, the task implements the Runnable interface; if there is a return val...

Asynchronous programming CompletableFuture detailed

Article directory 1. CompletableFuture Overview 2. How to use 2.1 Creating a CompletableFuture 2.2 Getting results 2.3 Conversion operation 2.4 Combination operation 2.5 Processing at completion 2.6 E...

CompletableFuture uses detailed explanation

1, runAsync and supplyAsync methods CompletableFuture provides four static methods to create an asynchronous operation. Methods that do not specify an Executor will execute the asynchronous code using...

More Recommendation

Thread CompletableFuture Detailed

CompletableFuture reference: Shortcomings of Future mode Although Future can fulfill the requirement of obtaining asynchronous execution results, it does not provide a mechanism for notification. We c...

Detailed use of CompletableFuture

CompletableFuture introduced CompletableFuture is a tool class introduced by java8 for asynchronous programming. We will introduce its powerful functions step by step based on a simple example of Comp...

Detailed explanation of CompletableFuture api

I spent two days learning CompletableFuture api, which is a class in the JUC atomic package. I ran all public api methods through the unit test code. I have a rough understanding of the underlying imp...

2.1 CompletAbleFuture Detailed

CompletableFuturejava architecture location CompleTableFuture implements the Future interface and the CompletionStage interface architecture diagram as follows What is COMPLETISTAGE? CompletionStage: ...

CompletableFuture detailed explanation ~ allof

When all stages are completed, create a stage The previous example was to deal with it after the completion of any stage of any stage. The next two examples demonstrated the completion of all stages b...

Copyright  DMCA © 2018-2026 - All Rights Reserved - www.programmersought.com  User Notice

Top