A brief discussion on CompletableFuture

About the author: Hello everyone, I am Brother Smart, a former architect of ZTE and Meituan, and now the CTO of an Internet company.

Contact qq: 184480602, add me to the group, let’s learn together, make progress together, and fight against the cold winter of the Internet together

ReviewFutureTask

We have already studied FutureTask and the thread pool inheritance system before, which introduced how the thread pool uses FutureTask to return asynchronous results:

The function of FutureTask#run() is to execute the task and set the final result to FutureTask.outcome:

In order to obtain the final result, FutureTask#get() will block internally until outcome is assigned:

Based on the above reasons, if you expect to get asynchronous results in actual programming, there are generally two ways:

  • FutureTask#get() blocks and waits
  • Judge FutureTask#isDone() and return if true

The first one is familiar to everyone, and the second one is demonstrated below:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CompletableFutureTest {

    private final ExecutorService executor = Executors.newFixedThreadPool(5);

    /**
     * 轮询异步结果并获取
     *
     * @throws ExecutionException
     * @throws InterruptedException
     */
    @Test
    public void testFutureAsk() throws ExecutionException, InterruptedException {

        // 任务1
        Future<String> runnableFuture = executor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Runnable异步线程开始...");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println("Runnable异步线程结束...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "fakeRunnableResult");

        // 任务2
        Future<String> callableFuture = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("Callable异步线程开始...");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("Callable异步线程结束...");
                return "callableResult";
            }
        });

        boolean runnableDone = false;
        boolean callableDone = false;

        // 不断轮询,直到所有任务结束
        while (true) {
            TimeUnit.MILLISECONDS.sleep(500);
            System.out.println("轮询异步结果...");
            if (runnableFuture.isDone()) {
                System.out.println("Runnable执行结果:" + runnableFuture.get());
                runnableDone = true;
            }
            if (callableFuture.isDone()) {
                System.out.println("Callable执行结果:" + callableFuture.get());
                callableDone = true;
            }
            if (runnableDone && callableDone) {
                break;
            }
        }

        System.out.println("任务全部结束");
    }
}

result

Runnable asynchronous thread starts...

Callable asynchronous thread starts...

Polling for asynchronous results...

Polling for asynchronous results...

Polling for asynchronous results...

Polling for asynchronous results...

Polling for asynchronous results...

Runnable asynchronous thread ends...

Callable asynchronous thread ends...

Polling for asynchronous results...

Runnable execution result: fakeRunnableResult

Callable execution result: callableResult

All tasks are over

Disadvantages of FutureTask

FutureTask is actually perfect in all aspects. It is even surprising when we first see it, because it allows us to obtain the results of asynchronous execution! But FutureTask#get() itself is blocking. Assume that there are currently three download tasks being executed:

  • task1 (estimated to take 5 seconds)
  • task2 (estimated to take 1 second)
  • task3 (estimated to take 1 second)

If task1.get() happens to be ranked first when blocking acquisition, it will cause a certain waste of resources, because task2 and task3 have already been prepared and can be taken out for processing first to obtain the best user experience.

Although we can improve it by combining polling + isDone() like the demo above, but there are still the following problems:

  • What is the appropriate polling interval?
  • In order to avoid while(true) blocking the main thread logic, you may need to enable separate thread polling, wasting a thread
  • Still can't handle complex task dependencies

Especially the third point, using FutureTask is almost difficult to write...which means that FutureTask is difficult to deal with asynchronous orchestration problems.

CompletableFuture: Future based on asynchronous callback

CompletableFuture VS FutureTask

Without further ado, let’s go straight to the code:

@Test
public void testCallBack() throws InterruptedException, ExecutionException {
    // 提交一个任务,返回CompletableFuture
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println("=============>异步线程开始...");
            System.out.println("=============>异步线程为:" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("=============>异步线程结束...");
            return "supplierResult";
        }
    });
    
	// 阻塞获取结果
    System.out.println("异步结果是:" + completableFuture.get());
    System.out.println("main结束");
}

result

=============>Asynchronous thread starts...

=============>The asynchronous thread is: ForkJoinPool.commonPool-worker-9

=============>Asynchronous thread ends...

The asynchronous result is: supplierResult

main ends

The whole process looks no different from synchronization, because we use CompletableFuture#get() in the main thread, which directly blocks it. Of course, you can also use CompletableFuture#isDone() to improve it, but we do not recommend that you use CompletableFuture as a FutureTask.

The two are so similar, is there any difference?

Similarities and differences between CompletableFuture and FutureTask:

  • Same: both implement the Future interface, so you can use methods such as Future#get(), Future#isDone(), Future#cancel(), etc.
  • different:
    • FutureTask implements Runnable, so it can be executed as a task, maintains the outcome internally, and can store the results.
    • CompletableFuture does not implement Runnable, cannot be executed as a task, so you cannot throw it directly to the thread pool for execution. On the contrary, you You can throw a functional interface implementation class like Supplier#get() to it for execution
    • CompletableFuture implements CompletionStage and supports asynchronous callbacks

In general, the biggest difference between FutureTask and CompletableFuture is that FutureTask requires us to actively block acquisition, while CompletableFuture supports asynchronous callbacks (described later).

If you compare the above code with the previous thread pool + Runnable/Callable, you will find that CompletableFuture actually seems to assume the role of the thread pool, and Supplier#get() corresponds to Runnable#run() and Callable#call() . But we have never seen CompletableFuture when analyzing the thread pool inheritance system. Supplier is just a preset functional interface of Java8, not a task class.

In other words, is not a thread pool's CompletableFuture + is not a task class< a i=4>’s functional interface instance actually handles asynchronous tasks!

so:

  • What exactly does CompletableFuture do underneath?
  • Why can it execute instances of functional interfaces as tasks? It's obviously neither Runnable nor Callable!
  • What is the relationship between CompletionStage and asynchronous callbacks?

CompletableFuture与CompletionStage

You may be unfamiliar with CompletionStage. It doesn’t matter. Let’s look at the code first:

@Test
public void testCallBack() throws InterruptedException, ExecutionException {
    // 提交一个任务,返回CompletableFuture(注意,并不是把CompletableFuture提交到线程池,它没有实现Runnable)
    CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println("=============>异步线程开始...");
            System.out.println("=============>异步线程为:" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("=============>异步线程结束...");
            return "supplierResult";
        }
    });

    // 异步回调:上面的Supplier#get()返回结果后,异步线程会回调BiConsumer#accept()
    completableFuture.whenComplete(new BiConsumer<String, Throwable>() {
        @Override
        public void accept(String s, Throwable throwable) {
            System.out.println("=============>异步任务结束回调...");
            System.out.println("=============>回调线程为:" + Thread.currentThread().getName());
        }
    });

    // CompletableFuture的异步线程是守护线程,一旦main结束就没了,为了看到打印结果,需要让main休眠一会儿
    System.out.println("main结束");
    TimeUnit.SECONDS.sleep(15);
}

result

=============>Asynchronous thread starts...

=============>The asynchronous thread is: ForkJoinPool.commonPool-worker-9

main ends

=============>Asynchronous thread ends...

=============>Asynchronous task end callback...

=============>The callback thread is: ForkJoinPool.commonPool-worker-9

You can temporarily understand each part of the method as submitting a task.

At this point, you should have two questions:

  • What is CompletionStage?
  • What does it have to do with asynchronous callbacks?

This section first answers the first question, leaving the second question to be explained in subsequent chapters (see the table of contents on the right <Implementation Mechanism of Asynchronous Callbacks>).

The main thread called CompletableFuture#whenComplete():

// 异步回调:上面的Supplier#get()返回结果后,异步线程会回调BiConsumer#accept()
completableFuture.whenComplete(new BiConsumer<String, Throwable>() {
    @Override
    public void accept(String s, Throwable throwable) {
        System.out.println("=============>异步任务结束回调...");
    }
});

In fact, this method is defined in the CompletionStage interface (there are so many methods):

public interface CompletionStage<T> {
    // 省略其他方法...
    
    /**
     * Returns a new CompletionStage with the same result or exception as
     * this stage, that executes the given action when this stage completes.
     *
     * <p>When this stage is complete, the given action is invoked with the
     * result (or {@code null} if none) and the exception (or {@code null}
     * if none) of this stage as arguments.  The returned stage is completed
     * when the action returns.  If the supplied action itself encounters an
     * exception, then the returned stage exceptionally completes with this
     * exception unless this stage also completed exceptionally.
     *
     * @param action the action to perform
     * @return the new CompletionStage
     */
    public CompletionStage<T> whenComplete
        (BiConsumer<? super T, ? super Throwable> action);
    
    // 省略其他方法...
}

And CompletableFuture implements whenComplete():

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    // 省略其他方法...
    
    public CompletableFuture<T> whenComplete(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(null, action);
    }
    
    private CompletableFuture<T> uniWhenCompleteStage(Executor e, BiConsumer<? super T, ? super Throwable> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<T> d = new CompletableFuture<T>();
        if (e != null || !d.uniWhenComplete(this, f, null)) {
            UniWhenComplete<T> c = new UniWhenComplete<T>(e, d, this, f);
            push(c);
            c.tryFire(SYNC);
        }
        return d;
    }
    
    // 省略其他方法...
}

So, what is CompletionStage?

My answer is:

  • It is a "very simple" interface. Completely independent, it does not inherit any other interface, and all methods are defined by itself.
public interface CompletionStage<T> {
    // 定义了超级多类似whenComplete()的方法
}
  • It is not a simple interface. Because while CompletableFuture implements Future, it also implements it. There are only 6 or 7 Future methods, and CompletionStage has a lot of methods, so if you open the source code of CompletableFuture, almost everything you see is its implementation of CompletionStage.
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    // 一些字段
    // 实现Future的方法
    
    // 实现CompletionStage的方法
    // 一些私有方法,配合CompletionStage
    // 一些内部类,配合CompletionStage
}
  • Asynchronous callbacks actually have a lot to do with CompletionStage (nonsense, FutureTask also implements Future, but cannot asynchronous callbacks)

All in all, CompletionStage is an interface that defines some methods. CompletableFuture implements these methods and designs an asynchronous callback mechanism.

How to do this will be revealed later.

a small detail

There is a detail that many people may not have noticed:

The comment above says:

Asynchronous thread willCallbackBiConsumer#accept()

There is nothing magical at first glance, but if you stop and think about it, you will find that what you just thought is:

The asynchronous thread will call back CompletableFuture#whenComplete()

When introducing CompletionStage above, I said "the main thread called CompletableFuture#whenComplete()", not an asynchronous thread call.

In other words, although methods such as whenComplete() defined in CompletionStage are related to asynchronous callbacks, they are not the methods that are ultimately called back. What is ultimately called back is actually BiConsumer#accept() passed in whenComplete(BiConsumer).

At this point, you may be confused. So what is the purpose of this CompletionStage that defines so many methods? Why does an asynchronous thread call back the method of the function interface it passes in? BiConsumer#accept() is obviously not Runnable#run() or Callable#call()!

Implementation mechanism of asynchronous callback

The above expressions seem to imply thatCompletableFuture#supplyAsync() will start an asynchronous thread, and then there will be a series of asynchronous callback operation.

In order to better understand the asynchronous callback of CompletableFuture, we further split the existing questions:

The main thread is on the left and the asynchronous thread is on the right.

Where do asynchronous threads come from, and how are Suppliers executed?

I have previously analyzed the similarities and differences between CompletableFuture and FutureTask, and one of them mentioned:

  • CompletableFuture does not implement Runnable and cannot be executed as a task, so you cannot throw it directly to the thread pool for execution. On the contrary, You can use Supplier#get() like this The functional interface implementation class is thrown to it for execution

So why can CompletableFuture execute "tasks"? Where do asynchronous threads come from? Why can Supplier be executed even if it does not implement Runnable/Callable?

Following the main thread into CompletableFuture#supplyAsync(), we will find:

Pay attention to the comments written by Doug Lea:

Returns a new CompletableFuture that is completed asynchronously by a task running in {@link ForkJoinPool#commonPool()}, whose value is obtained by calling the given Supplier.

In general, it is a bit similar to FutureTask, in that some parameters are passed in and a Future is returned.

From Doug Lea’s comments, we can get a glimpse of some important information:

  • Asynchronous threads come from the ForkJoinPool thread pool
  • Pass in the Supplier through CompletableFuture#supplyAsync(supplier) and return the CompletableFuture object, which contains a future value, and this value will be generated later by the asynchronous thread executing Supplier#get()

How to verify whether what the boss said is correct?

Just kidding, when it comes to the back-end, which one gets the front-end intervention?

Come, let’s look at the source code together~

We can see that CompletableFuture#supplyAsync(supplier) internally calls asyncSupplyStage(asyncPool, supplier). At this time, a thread pool asyncPool is passed in, which is a member variable of CompletableFuture:

ForkJoinPool will be used when useCommonPool is true, and useCommonPool depends on whether the hardware running the current program supports multi-core CPUs. You can check the source code for details.

Now that we have determined that the asynchronous thread comes from ForkJoinPool, the remaining question is that the Supplier passed in by the main thread does not implement the Runnable/Callable interface at all. How can it be executed by the asynchronous thread?

Oh ~ the same routine as ExecutorService#submit(): wrap it into a Task and then execute it. Only this time it is packaged as AsyncSupply instead of FutureTask:

Although the name of AsyncSupply is weird, it is quite similar to the original FutureTask. Both implement Future and Runnable, and have the dual attributes of task + result:

And then there’s the familiar recipe:

When the thread pool allocates a thread, AsyncSupply#run() will eventually be executed:

The asynchronous thread will execute AsyncSupply#run() and call f.get() within the method, that is, Supplier#get(), blocking to obtain the result and setting the value to CompletableFuture through d.completeValue(v), and CompletableFuture d has been Returned in the previous step asyncSupplyStage(). The final effect is the same as thread pool + FutureTask. The Future instance is returned first, and then the value is put in by reference.

Therefore, completableFuture.get() can block to get the result:

So far, we understand how asynchronous threads come from and how Supplier is executed.

From this perspective,CompletableFuture is equivalent to a Future with its own thread pool, while CompletableFuture#supplyAsync(Supplier) is ExecutorService#submit(Runnable/Callable) will also package the task internally and finally throw it to Executor#execute(Task). It's just that ExecutorService wraps Runnable#run()/Callable#call() into FutureTask, while CompletableFuture wraps the messy Supplier#get() and other functional interface methods into ForkJoinTask.

The principle of asynchronous callback

Blocking get() is no longer surprising. The key is how to implement the callback mechanism?

Before introducing the callback mechanism of CompletableFuture, let me explain to you that callbacks are not as magical as everyone thinks, especially the callback mechanism of CompletableFuture. In fact, it is essentially the sequential execution of multiple internal functions of CompletableFuture, but the initiator is an asynchronous thread. Instead of the main thread:

Now the first problem has been solved:

Questions 2 and 3 are actually the same question and will be discussed together.

In order to better explain the problem, we replaced the original CompletableFuture#whenComplete() in the second part with CompletableFuture#thenApply(). The essence is the same. By the way, we are familiar with other methods (also the implementation of CompletableFuture for CompletionStage):

@RunWith(SpringRunner.class)
@SpringBootTest
public class CompletableFutureTest {

    @Test
    public void testCallBack() throws InterruptedException {
        // 任务一:把第一个任务推进去,顺便开启异步线程
        CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("=============>异步线程开始...");
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("=============>completableFuture1任务结束...");
                System.out.println("=============>执行completableFuture1的线程为:" + Thread.currentThread().getName());
                return "supplierResult";
            }
        });
        System.out.println("completableFuture1:" + completableFuture1);

        // 任务二:把第二个任务推进去,等待异步回调
        CompletableFuture<String> completableFuture2 = completableFuture1.thenApply(new Function<String, String>() {
            @Override
            public String apply(String s) {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("=============>completableFuture2任务结束 result=" + s);
                System.out.println("=============>执行completableFuture2的线程为:" + Thread.currentThread().getName());
                return s;
            }
        });
        System.out.println("completableFuture2:" + completableFuture2);

        // 任务三:把第三个任务推进去,等待异步回调
        CompletableFuture<String> completableFuture3 = completableFuture2.thenApply(new Function<String, String>() {
            @Override
            public String apply(String s) {
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("=============>completableFuture3任务结束 result=" + s);
                System.out.println("=============>执行completableFuture3的线程为:" + Thread.currentThread().getName());
                return s;
            }
        });
        System.out.println("completableFuture3:" + completableFuture3);

        System.out.println("主线程结束");
        TimeUnit.SECONDS.sleep(40);
    }
}

result

completableFuture1:java.util.concurrent.CompletableFuture@76e4212[Not completed]

=============>Asynchronous thread starts...

completableFuture2:java.util.concurrent.CompletableFuture@23121d14[Not completed]

completableFuture3:java.util.concurrent.CompletableFuture@72af90e8[Not completed]

Main thread ends

=============>completableFuture1 task ends...

=============>The thread executing completableFuture1 is: ForkJoinPool.commonPool-worker-9

=============>completableFuture2 task ends result=supplierResult

=============>The thread executing completableFuture2 is: ForkJoinPool.commonPool-worker-9

=============>completableFuture3 task ended result=supplierResult

=============>The thread executing completableFuture3 is: ForkJoinPool.commonPool-worker-9

Analyze the backbone of the main thread:

  • CompletableFuture#supplyAsync(Supplier): Wrap Supplier as AsyncSupply, call executor.execute(), and wait for the asynchronous thread to call back Supplier#get()
  • CompletableFuture#thenApply(Function)
  • CompletableFuture#thenApply(Function)

When I introduced CompletionStage before, I felt sorry for this old man and introduced it ceremoniously. It turned out that CompletionStage#whenComplete() and CompletionStage#thenApply() were called by the main thread instead of asynchronous callbacks. So, what exactly does the main thread do when it calls whenComplete(BiConsumer) and thenApply(Function), causing the asynchronous thread to eventually execute BiConsumer#accept() and Function#apply()?

Please change the sleep time in the above code to 100 seconds to facilitate analysis time.

Then follow me and make a few breakpoints:

点进CompletableFuture#thenApply(Function):

OK, start the test case in DEBUG mode, experience the execution process of Main several times, and stop the program to familiarize yourself with the code if necessary.

five minutes later...

You must have walked through it several times, right? Let's take a look.

I believe everyone is very impressed by uniApply(), becauseFor the main thread this method is almost equivalent to not being executed, every time returned false.

CompletableFutureTest currently has three pieces of code: task one, task two, and task three.

When the main thread executes CompletableFuture#supplyAsync(Supplier) in "Task One", it packages the Supplier into an AsyncSupply task and starts the asynchronous thread. After that,asynchronous thread will block in Supplier#get():

In other words, Supplier#get() is the first stop of execution after the asynchronous thread is started!

At the same time, the main thread continues to execute the following "Task 2" and "Task 3", and both reach uniApply(), and both return false because a.result==null.

Taking "Task 2" as an example, when the main thread comes in from Task 2, thenApply() is called:

We will eventually reach uniApply(). Through the console log, we find that a is actually completableFuture1:

Because this was passed in in the previous step of uniApply():

That is to say:

主线程 ---> completableFuture1.thenApply(Function#apply) ---> !d.uniApply(this, f#apply, null)

a.result is completableFuture1.result, and the value of completableFuture1 comes from Supplier#get(), which is indeed null at this time (it will happen after the asynchronous thread blocks for 100 seconds).

So at this time d.uniApply(this, f, null) is false, then !d.uniApply(this, f, null) is true, and the if statement will be entered:

Mainly did 3 things:

  • Input Executor e, new CompletableFuture d, current completableFuture1, Function f, Build UniApply
  • push(uniApply)
  • uniApply.tryFire(SYNC)

Task 1 does two things:

  • Start asynchronous thread
  • Wait for callback

Since the thread needs to be started, it also needs to be executed as a task, so Supplier#get() is packaged into AsyncSupply, which is a Task. The subsequent tasks actually only do one thing: wait for callbacks. As long as the method can be executed through the instance, it is different from Task 1, so it is just packaged into a UniApply object.

push(uniApply) is thought to wrap Function#apply() of task 2 and stuff it into task stack.

But what does uniApply.tryFire(SYNC) do? uniApply() is called again inside:

SYNC=0, so the final judgment is !d.uniApply(this, f, this) ==true, tryFire(SYNC) returns null, and the following d.postFire(a, mode)It will not be executed at this time. When the asynchronous thread recovers later and is called again with the result of task one, the effect will be completely different.

To summarize, the operations of "Task 2" and "Task 3" are the same, and they both do three things:

  • The main thread calls CompletableFuture#thenApply(Function f) and passes in f, builds the UniApply object and wraps Function#apply()
  • Push the constructed UniApply object to the stack
  • Return CompletableFuture d

The green one is the asynchronous thread. At this time, it is blocked and waiting for Supplier#get(), but the main thread is not idle and is working hard to build the task stack.

After waiting 100 seconds, after Supplier#get() in supplyAsync(Supplier) returns the result, the asynchronous thread continues:

Did you see that postComplete() will also go to uniApply(), but this time there is already an asynchronous result result, so the process will not be truncated, and Function#apply(s) will eventually be called, and this s is The execution result of the previous function. In other words, the new CompletableFuture object calls Function#apply() to process the results generated by the previous CompletableFuture.

Finally, I want to save my face for Brother CompletionStage, you are the best:

The purpose of methods such as CompletableFuture#whenComplete(BiConsumer) and CompletableFuture#thenApply(Function) is to encapsulate callback functions such as BiConsumer#accept() and Function#apply() into UniApply objects that are pushed onto the stack. When the asynchronous thread is executed, Then pop the stack one by one and call back.

The black line goes first, and the green asynchronous thread blocks for a while before leaving. At this time, the main thread has successfully built the task stack and just guides the asynchronous thread to execute.

Therefore, in general, CompletionStage is designed very cleverly. Mr. Doug Lea is worthy of being the man who independently designed JUC. His data structure skills and understanding of programming are unparalleled in the world.

Of course, CompletableFuture has many other APIs, and you can even open an asynchronous thread during the callback task. This article only analyzes supplyAsync()+thenApply(), but the principles are roughly the same. To be honest, the internal implementation mechanism is relatively complicated. I personally do not recommend continuing to study the source code in depth, as it is of little significance.

Comparison of the number of threads between CompletableFuture and FutureTask

The number of threads consumed by CompletableFuture and FutureTask is the same, but for FutureTask, whether it is polling or blocking get, the main thread will be unable to continue other tasks, or the main thread can continue other tasks, but it must check from time to time whether the FutureTask has been Completing the task is rather frustrating. CompletableFuture will call back one by one according to the order we arranged, and it will be executed according to the established route.

In fact, no matter which method is used, the asynchronous thread actually needs to block and wait for the result, and cannot handle other tasks during this period. But for FutureTask, under the premise that asynchronous threads are destined to be unable to be reused, if you want to obtain the final result, you need tothe main thread actively query or open it additionally A thread query, and may cause blocking, and CompletableFuture's asynchronous task execution and task result acquisition are allasynchronous threadsindependentDone.

so:

1 asynchronous thread blocks the execution of the task + callback asynchronous result > 1 asynchronous thread blocks the execution of the task + 1 thread blocks the query task

question

In view of the long length and in-depth content, I will set some questions to force everyone to think about and organize.

Suppose you have the following code:

public class CompletableFutureTest {

    @Test
    public void testCallBack() throws InterruptedException {
        // 任务一
        CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                return "supplierResult";
            }
        });
        System.out.println("completableFuture1:" + completableFuture1);

        // 任务二
        CompletableFuture<String> completableFuture2 = completableFuture1.thenApply(new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s;
            }
        });
        System.out.println("completableFuture2:" + completableFuture2);

        // 任务三
        CompletableFuture<String> completableFuture3 = completableFuture2.thenApply(new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s;
            }
        });
        System.out.println("completableFuture3:" + completableFuture3);

        System.out.println("主线程结束");
        TimeUnit.SECONDS.sleep(40);
    }
}
  • What operations does the main thread do when executing task one, task two, and task three?
  • Which methods are executed by the main thread and which methods are executed by asynchronous threads?
  • When Function#apply(String s) is called back, where do the formal parameters come from?
  • What is the correspondence between the return value CompletableFuture and the asynchronous result?
About the author: Hello everyone, I am Brother Smart, a former architect of ZTE and Meituan, and now the CTO of an Internet company.

Join the group, let’s learn together, make progress together, and fight the Internet winter together

Guess you like

Origin blog.csdn.net/smart_an/article/details/134918492