A report needs to be prepared in recent days. Data of each group in the report is obtained from the ELK. Since it takes a period of time to query the ELK, it takes a lot of time to process task stacking in sequence. After the interface was finished, the access speed was very slow and THERE was no good optimization idea. Until the boss reconstructed the code for me, the access speed of the interface was about 5 times faster, and the boss mocked the bad code. This who suffered ah, have to change to learn, we will learn the Future asynchronous first today, this optimization can not trouble boss.

Introduction to Future asynchronous programming

Future represents the result of asynchronous computation. The Future provides methods to check that the calculation is complete, wait for the calculation to complete, and retrieve the result of the calculation. You can retrieve the results using the GET method, which retrieves the results after the calculation is complete. Its cancel method performs the cancellation operation. What it does is, you do something else, and when I’m done, I’ll hand it off to you for follow-up.

An example of a fictional future:

interface ArchiveSearcher { String search(String target); } class App { ExecutorService executor = ... ArchiveSearcher searcher = ... void showSearch(String target) throws InterruptedException { Callable<String> task = () -> searcher.search(target); Future<String> future = executor.submit(task); displayOtherThings(); // do other things while searching try { displayText(future.get()); // use future } catch (ExecutionException ex) { cleanup(); return; }}}Copy the code

DisplayText (future.get()) The future.get() value needed in this code is retrieved from the future by get(), which blocks the program until the value is ready.

Another example

public class SquareCalculator { private ExecutorService executor = Executors.newSingleThreadExecutor(); public Future<Integer> calculate(Integer input) { return executor.submit(() -> { Thread.sleep(1000); return input * input; }); }}Copy the code

Callable is an interface that represents a task that returns a result, in this case using the lambda expression () -> {thread.sleep (1000); return input * input; } creates an instance of it. The Callable instance needs to be passed to an Executor, which is responsible for starting the task in a new thread and returning a Future result object.

There are several ways to obtain ExecutorService instances, one of which is provided by the Execoring-like Static factory method. In this example, we use the basic newSingleThreadExecutor(), which creates an execuorService that operates on a queue using a single thread.

Once we have the ExecutorService object, we simply call submit(), passing the callable object as a parameter. Submit () is responsible for starting the task and returning a FutureTask object.

The FutureTask class is an implementation of a Future that implements Runnable and can therefore be executed by an executor.

Common methods for Future

Use isDone() or get() to get the results

Now we need to call the calculate() method and get the calculated result of type Integer from the returned Future object. There are two methods in the Future API to help us do this.

Future.isdone () tells us whether the executor has finished processing the task. Returns true if the task has completed, false otherwise.

Future.get() is used to get the actual results. Note that this method blocks the process until the task is calculated and results are returned. It is best to use the isDone() method first to determine whether the calculation is complete and prevent the program from blocking other tasks.

Future<Integer> future = new SquareCalculator().calculate(10); while(! future.isDone()) { System.out.println("Calculating..." ); Thread.sleep(300); } Integer result = future.get();Copy the code

The get() method also has an overloaded method.

Integer result = future.get(500, TimeUnit.MILLISECONDS);Copy the code

The difference between GET (long, TimeUnit) and get() is that the former will throw a TimeoutException if the task does not return before the specified timeout.

Cancel the task using cancel()

Suppose we have triggered a task, but for some reason we no longer care about the outcome. We can use future.cancel (Boolean) to tell the executing program to stop the operation and interrupt its underlying thread

Future<Integer> future = new SquareCalculator().calculate(4);

boolean canceled = future.cancel(true);Copy the code

If we try to call get() after calling Cancel (), a CancellationException occurs. We can use future.iscancelled () to see if a Future has been cancelled.

Calling Cancel () may fail, in which case false is returned. Cancel () takes a Boolean value as an argument that controls whether or not the thread performing the task should be interrupted.

Implementing multithreading

Our current ExecutorService is single-threaded, because it is through the Executors newSingleThreadExecutor. To make it clear if it is a single thread, let’s trigger both task calculations at the same time.

SquareCalculator squareCalculator = new SquareCalculator(); Future<Integer> future1 = squareCalculator.calculate(10); Future<Integer> future2 = squareCalculator.calculate(100); while (! (future1.isDone() && future2.isDone())) { System.out.println( String.format( "future1 is %s and future2 is %s", future1.isDone() ? "done" : "not done", future2.isDone() ? "done" : "not done" ) ); Thread.sleep(300); } Integer result1 = future1.get(); Integer result2 = future2.get(); System.out.println(result1 + " and " + result2); squareCalculator.shutdown();Copy the code

Analyze the return results

calculating square for: 10
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
calculating square for: 100
future1 is done and future2 is not done
future1 is done and future2 is not done
future1 is done and future2 is not done
100 and 10000Copy the code

We can see that the two tasks are not parallel, the second task will start after the second task is completed, the whole process takes about 2 seconds.

The program to make our multi-threaded run in parallel, we need to use support multithreading ExecutorService, we can use the Executors. NewFixedThreadPool () to implement multithreaded, this method can be passed to open the number of threads.

public class SquareCalculator { private ExecutorService executor = Executors.newFixedThreadPool(2); / /... }Copy the code

Now we have an executor that can use two threads at once. If we run the exact same client code again, we get the following output

calculating square for: 10
calculating square for: 100
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
future1 is not done and future2 is not done
100 and 10000Copy the code

Now it looks like the two tasks start and finish running at the same time, and the whole process takes about a second to complete.

There are other factory method can be used to create a thread pool, such as Executors. NewCachedThreadPool (), it use threads available before reuse, and Executors. NewScheduledThreadPool (), and its scheduling command to run after a given delay.

Interesting use of Future

Another example of calculating factorials using futures is factorialCalculator.java

import java.math.BigInteger; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; public class FactorialCalculator implements Callable<BigInteger> { private int value; public FactorialCalculator(int value) { this.value = value; } @Override public BigInteger call() throws Exception { var result = BigInteger.valueOf(1); if (value == 0 || value == 1) { result = BigInteger.valueOf(1); } else { for (int i = 2; i <= value; i++) { result = result.multiply(BigInteger.valueOf(i)); } } TimeUnit.MILLISECONDS.sleep(500); Return result; }}Copy the code

The FactorialCalculator calculates factorials using BigInteger.

public class FactorialCalculator implements Callable<BigInteger> {Copy the code

The FactorialCalculator implements a Callable object. The Callable object represents an asynchronous task that returns a result. In our case, the result is calculated factorial.

Use the Future object javafutureex.java

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

public class JavaFutureEx {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
 
        

        var random = new Random();

        for (int i = 0; i < 6; i++) {

            int number = random.nextInt(100) + 10;
            var factorialCalculator = new FactorialCalculator(number);

            Map<Integer, Future<BigInteger>> result = new HashMap<>();
            result.put(number, executor.submit(factorialCalculator));
            resultList.add(result);
        }

        for (Map<Integer, Future<BigInteger>> pair : resultList) {

            var optional = pair.keySet().stream().findFirst();

            if (!optional.isPresent()) {
                return;
            }

            var key = optional.get();

            System.out.printf("Value is: %d%n", key);

            var future = pair.get(key);
            var result = future.get();
            var isDone = future.isDone();

            System.out.printf("Result is %d%n", result);
            System.out.printf("Task done: %b%n", isDone);
            System.out.println("--------------------");
        }

        executor.shutdown();
    }
}Copy the code

Code parsing

var executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);Copy the code

Var is a local variable definition method introduced in Java 10. Executor services handle the life cycle of asynchronous tasks. Its Submit () accepts both callable and runable objects.

var factorialCalculator = new FactorialCalculator(number);Copy the code

A FactorialCalculator task is created. It will run asynchronously.

Map<Integer, Future<BigInteger>> result = new HashMap<>();
result.put(number, executor.submit(factorialCalculator));
resultList.add(result);Copy the code

We submit the task to executor and put the integer value and future in a Map, so we have a Map of the value and the calculated factorial. We can look at the list of results and notice that the future is returned before it has been evaluated.

var optional = pair.keySet().stream().findFirst(); if (! optional.isPresent()) { return; } var key = optional.get();Copy the code

We can use the get() method to get the value of the computed result, and when we call get(), processing is blocked until we get the value.

var future = pair.get(key);
var result = future.get();Copy the code

The above example returns the following result

Value is: 39 Result is 20397882081197443358640281739902897356800000000 Task done: true -------------------- Value is: 99 Result is 933262154439441526816992388562667004907159682643816214685929638952175999932299156089414639761565182862536979208272237582 511852109168640000000000000000000000 Task done: true -------------------- Value is: 39 Result is 20397882081197443358640281739902897356800000000 Task done: true -------------------- Value is: 102 Result is 961446671503512660926865558697259548455355905059659464369444714048531715130254590603314961882364451384985595980362059157 503710042865532928000000000000000000000000 Task done: true -------------------- Value is: 12 Result is 479001600 Task done: true -------------------- Value is: 49 Result is 608281864034267560872252163321295376887552831379210240000000000 Task done: true --------------------Copy the code

conclusion

Through the above two examples, we should be able to master the common use of Future asynchronous programming, which can be used in daily programming processing is sufficient, if you need to understand the implementation of Future can learn Fork/Join Framework in Java, In addition, There is an implementation of The CompletableFuture, which was added in Java 8. There are more API methods To implement complex operations. See The Guide To CompletableFuture for more details.

The resources

  • Java Futrure Tutorial
  • Java 11 official documentation