JAVA Concurrent Programming -- Introduction and use of Future and completable Future

1. Introduction to future

2. Use of future class

3. Introduction to completabilefuture class

4. Use of completabilefuture class

5. Summary

1. Introduction to future
We were in this article before JAVA Concurrent Programming -- Introduction and use of Callable interface and FutureTask As mentioned in, Future is an asynchronous processing thread class that can obtain the return value.
Before, whether we inherited the Thread class or implemented the Runnable interface, we couldn't get the return value of the Thread, but we can do it in the future. Future is a Thread with a return value, which we may not understand. Let's use a production case to illustrate this example.

2. Use of future class
Suppose we have such a requirement:

Let's start with a page:

This is a picture of a large data screen found on the Internet. What would you do if the data on this screen were to be queried and returned?

We know that the business logic to be queried is different due to the different areas displayed on the large screen. It can be divided into the following areas:

If you are a novice now, you may query area by area in series. After area one is checked, check area two, then area three, and then return to the front end. This is not efficient.

Improvement method: query multiple regions synchronously, and finally return to the front end (multiple arrows fired at the same time).

So it's still abstract. Let's take a picture as an example:

It should be explained with this picture. Next, we use the Future class to show the code:

public DataResult queryData() throws ExecutionException, InterruptedException, TimeoutException {
      
        DataResult result = new DataResult();
        //Query area 1
        FutureTask<Integer> allHumanNum = new FutureTask<>(() -> queryHumanNum());
        //Using thread pool execution
        CalculateThreadPoolConfig.getThreadPoolExecutor().submit(allDoctorNum);
        //Query area 2
        FutureTask<Integer> allMaleNum = new FutureTask<>(() -> queryAllMaleNum());
       //Use thread pool to execute calculatethreadpoolconfig getThreadPoolExecutor(). submit(queryInvitedDoctorNum);
       //And so on
       //......

        //Splice result set
        result.setAllHumanNum(allHumanNum.get(5, TimeUnit.SECONDS));
        result.setAllMaleNum(allMaleNum.get(5, TimeUnit.SECONDS));
        return result;
    }

It can be seen that no matter how many data sets we query, only the data set that takes the longest query time is enough, which realizes the effect of synchronous parallel query and greatly reduces the query time.

However, we should pay attention to this line of code:

allHumanNum.get(5, TimeUnit.SECONDS);

get means to block the returned results all the time, but adding a number and unit means to wait only 5 seconds, otherwise an exception will be thrown.
When we work, if we need to call someone else's interface to return data, but we don't know how long the other party has to calculate, maybe the other party's interface will drag down the whole function we write. In order to avoid this situation, in order to protect ourselves at work, we need to learn to add a layer of insurance. If the other party's interface does not return in five seconds, it will be out of date.

3. Introduction to completabilefuture class

After introducing the FutreTask class, we know that although it is a very useful class, it also has many disadvantages, such as:
1.get() is easily blocked
2. If you want to combine multiple result sets into one result set, you need many futuretasks
3.FutureTask can only be used for simple one-step calculation. Two asynchronous calculations cannot be combined into one asynchronous calculation. The two asynchronous calculations are independent of each other, and the second depends on the result of the first.
4. When a task in the Future set ends the fastest, the result will be returned.
wait

There are still many shortcomings. At this time, we can use the completabilefuture class!

Completable Future makes up for the shortcomings of the Future model. It can combine multiple result sets into a result set, and can divide an asynchronous task into several stages for calculation. The later results depend on the previous results, and so on.

In a word, what FutureTask can do, completable future can also do, and completable future can do more!

Let's search the class completabilefuture first

It implements the Future interface and CompletionStage interface.

CompletionStage: represents a stage in the asynchronous computing process. After one stage is completed, another stage may be triggered.

Let's take a closer look at the methods owned by CompletionStage. According to theapply, whenComplete, exceptionally and other methods, we can roughly see that this class can program each stage of a calculation and complete the next step first, (according to thenApply) execute the next step, (whenComplete) what will happen when it is finished, (exceptionally) what will happen if an exception is thrown, etc.

Like a call chain, this is called chain programming.

Next, we use a case to carry out actual combat with completable future.

4. Use of completabilefuture class

Suppose we have an e-commerce price comparison demand:
We need to collect the price of the book Think in java from major websites, open multiple threads to collect from major websites, output information to the console after collection, and output information after thread execution. Please use completable future and chain programming to solve this problem.

Let's start programming:

public class CompletableFutureNextMallDemo {
 // This is the price website that needs to be collected
 static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("pdff"),
            new NetMall("tmall"),
            new NetMall("xhs"),
            new NetMall("xy"),
            new NetMall("zh"),
            new NetMall("sf")
    );

}
class NetMall {
    // This is the specific category of the website and the method of calculating the price
    private String mallName;

    public NetMall() {
    }

    public NetMall(String mallName) {
        this.mallName = mallName;
    }

    public String getMallName() {
        return mallName;
    }

    public void setMallName(String mallName) {
        this.mallName = mallName;
    }

    //Method of calculating price
    public double calcPrice(String productName) {
        try {
            //Suppose this is the calculation process
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }

}

The most critical execution method

    //Chain scheduling multiple arrows at once
    public static List<String> getPriceByASync(List<NetMall> list, String productName) {

        //Loop through all websites, and each website starts a thread for calculation
        //CompletableFuture.supplyAsync represents the execution process with return value, which is equivalent to the get(0) method of Future
        return list.stream().map(netMall -> CompletableFuture.supplyAsync(() -> {
            return String.format(productName + " in %s price is %.2f", netMall.getMallName(), netMall.calcPrice(productName));
        }
        //When the first step is completed, the second step is to output the book price
        ).thenApply(r-> {
            System.out.println(r);
            return r;
        }
        //When the task is completed, the output of the website calculation is completed
        ).whenComplete((v,e)-> {
            System.out.println(netMall.getMallName()+"Calculation completed!");
        }
        //When an exception is thrown, an exception is output
        ).exceptionally(e->{
            System.out.println("Failed to get, exception occurred!" );
            e.printStackTrace();
            return null;
        })).collect(Collectors.toList())
           .stream()
           //Finally, uniformly call the join method to return the results
           .map(CompletableFuture::join)
           //Finally, it is merged into a List and returned as a result
           .collect(Collectors.toList());
    }

Here, we extract the completable future and find that the code will be as follows:

CompletableFuture
.supplyAsync()//Execute asynchronous processing with return value, which is equivalent to get() of FutureTask
.thenApply()//Proceed to step 2
.whenComplete()//Logic triggered when completed
.exceptionally();//Logic triggered when an exception occurs

5. Summary

Through today's study, we have learned about the basic theory and practice of Future and completable Future. These are two classes that are very easy to use. I hope we can use them more in our work, but don't use multithreading blindly, because using more than one tool will bring more risks.

Keywords: Java Multithreading

Added by localhost1 on Fri, 24 Dec 2021 07:12:28 +0200