The role of the Future

The Future is the most important role, for example, as a certain operation, operation process can be time-consuming and sometimes to check the database, or heavy computation, such as compression, encryption, etc., in this case, if we have been in place waiting for the returns, obviously is not wise, the running efficiency of the overall program will be greatly reduced. We can put the calculation process into the child thread to execute, and then control the calculation process executed by the child thread through the Future, and finally obtain the calculation result. In this way, the efficiency of the whole program can be improved, which is an asynchronous idea.

The relationship between Callable and Future

Now let’s look at the relationship between Callable and Future. As mentioned earlier, one of the main advantages of Callable over Runnable is that it can return a result. You can get it using the Future class get method. Thus, the Future is equivalent to a store that stores the task results of the Callable’s Call method. In addition, we can also determine whether a task has been completed by using the isDone method of the Future, cancel the task by using the cancel method, or obtain the result of the task within a limited time, etc. In short, the Future has rich functions. With this macro concept in mind, let’s look at the main methods of the Future class in detail.

Methods and usage of Future

Let’s take a look at the code for the Future interface. There are five methods, as follows:

public interface Future<V{



    boolean cancel(boolean mayInterruptIfRunning);



    boolean isCancelled(a);



    boolean isDone(a);



    get(a) throws InterruptedException, ExecutionException;



    get(long timeout, TimeUnit unit)

        throws InterruptedException, ExecutionException, TimeoutExceptio

}

Copy the code

The fifth method is an overloading of the fourth method, with the same name but different parameters.

Get () method: gets the result

The main function of GET method is to obtain the result of task execution. The behavior of this method during execution depends on the status of Callable task. The following five situations may occur.

(1) The most common one is that when get is executed, the task has been completed and can be returned immediately to obtain the execution result of the task.

(2) It is possible that the task has no result. For example, if we put a task into the thread pool, there may be a backlog of tasks in the thread pool and I get them before it is my turn to execute them. In this case, the task has not started. There is also a case where the task is in progress, but the execution process is long, so when I go to get, it is still in progress. When we call GET, whether the task has not started or is in progress, we block the current thread until the task is complete and then return the result.

(3) An exception is thrown during the execution of the task. Once this happens, when we call get, we will throw an ExecutionException, regardless of the type of exception thrown when we execute the call method. The exceptions you get when you execute the GET method are executionExceptions.

(4) The task is cancelled. If the task is cancelled, a CancellationException will be thrown when we use get to get the result.

(5) The task timed out. We know that the GET method has an overloaded method with a delay parameter. After calling the GET method with a delay parameter, if the call method successfully completes the task within the specified time, then get will return normally. However, if the task has not been completed by the specified time, the GET method throws a TimeoutException indicating that a timeout has occurred.

As shown, it is easier to understand;

In the figure, on the right is a thread pool with a number of threads to perform tasks. This Task implements the Callable interface. When we submit this Task to the thread pool, calling the Submit method will immediately return an object of type Future. This object is currently empty and does not contain the result of the calculation because the calculation has not yet been completed.

Once the calculation is complete, when we can retrieve the result, the thread pool fills the result into the previously returned Future (the F object) rather than creating a new Future at this point. You can then use the Future’s get method to get the result of the task.

/** * description: Demonstrates the use of a Future */

public class OneFuture {



    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);

        Future<Integer> future = service.submit(new CallableTask());

        try {

            System.out.println(future.get());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

        service.shutdown();

    }



    static class CallableTask implements Callable<Integer{



        @Override

        public Integer call(a) throws Exception {

            Thread.sleep(3000);

            return newRandom().nextInt(); }}}Copy the code

In this code, the main method creates a pool of 10 threads and uses the Submit method to submit a task. This task, shown at the bottom of the code, implements the Callable interface and all it does is sleep for three seconds and then return a random number. We then print the future.get result directly, which normally prints a random number, such as 100192, etc. This code corresponds to the illustration we just showed you, and it’s one of the most common uses of Future.

IsDone () method: check whether the execution is complete

Let’s move on to some other methods of the Future, such as the isDone() method, which is used to determine if the current task is completed.

Note that if this method returns true, execution is complete; If false is returned, it is not finished. However, a return of true does not mean that the task was successfully executed, for example, an exception was thrown in the middle of the task. So in this case, for this isDone method, it’s actually going to return true, because for it, even though there was an exception, the task is not going to be executed in the future, it’s actually done. So when the isDone method returns true, it does not mean that the task was successfully executed, only that it completed.

public class GetException {



    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(20);

        Future<Integer> future = service.submit(new CallableTask());





        try {

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

                System.out.println(i);

                Thread.sleep(500);

            }

            System.out.println(future.isDone());

            future.get();

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch(ExecutionException e) { e.printStackTrace(); }}static class CallableTask implements Callable<Integer{



        @Override

        public Integer call(a) throws Exception {

            throw new IllegalArgumentException("Callable throws an exception"); }}}Copy the code

In this code, you can see that there is a thread pool, and submitting a task to the thread pool will directly throw an exception. So let’s go to sleep with a for loop, and let it slowly print out the five digits from 0 to 4, so that we have some delay. After this isDone, call isDone() and print out the result, then call future.get().

We know that this exception is actually thrown as soon as the task is executed, because there is no other logic in our computational task except to throw the exception. Now, when does the console print an exception? It prints the exception only after true is printed, that is, when the GET method is called.

Cancel Cancels the task

Let’s look at the cancel method again. If you don’t want to perform a task, you can use the cancel method. There are three cases:

The first case is the simplest, in which the task has not yet been executed. Once cancel is called, the task is cancelled normally and will not be executed in the future, and the Cancel method returns true.

The second case is also simpler. If the task has completed or has been cancelled before, cancel fails and returns false. Because tasks, whether completed or cancelled, can’t be cancelled again.

The third case is special, that is, the task is being executed. In this case, executing the cancel method will not cancel the task directly, but will make a judgment according to the parameters we pass in. The cancel method must pass in a parameter called mayInterruptIfRunning. What does it mean? If true is passed in, the thread executing the task will receive an interrupt signal, and the executing task will probably have some logic to handle the interrupt and stop, which makes sense. If false is passed in, the running task is not interrupted, that is, the cancel has no effect and the Cancel method returns false.

So how do you choose to pass in true or false?

  • If we know explicitly that the thread cannot handle interrupts, we should pass false.
  • We don’t know if this task supports cancellation (or can respond to interrupts), because in most cases the code is collaborative and we can’t be sure that this task supports interrupts, so we should pass false in that case as well.
  • Once the task is running, we want it to be fully executed. In this case, false should also be passed.

This is the different meaning and selection method of passing true and false.

IsCancelled () method: Determine whether it has been cancelled

The final method is the isCancelled method, which determines whether it has been cancelled and is used in conjunction with the cancel method, which is relatively simple.

So that’s the main approach to Future.

Use FutureTask to create a Future

In addition to using the Thread pool’s Submit method to return a Future object, you can also use FutureTask to retrieve the results of the Future class and task.

FutureTask is first a Task and then has the semantics of a Future interface because it can be executed in the Future.

FutureTask code implementation:

public class FutureTask<Vimplements RunnableFuture<V>{... }Copy the code

It implements an interface called RunnableFuture. Let’s look again at the code implementation of the RunnableFuture interface:

public interface RunnableFuture<Vextends Runnable.Future<V{

    void run(a);

}
Copy the code

Runnable, Future, RunnableFuture, FutureTask diagram

Since RunnableFuture inherits the Runnable and Future interfaces, and FutureTask implements the RunnableFuture interface, FutureTask can be executed by threads as Runnable, You can also get the return value of the Callable as a Future.

Typically, you take a Callable instance as an argument to the FutureTask constructor, generate a FutureTask object, and then treat that object as a Runnable object, place it in a thread pool, or start another thread to execute it. Finally, you can obtain the results of the task execution through FutureTask.

Ex. :

/** * Description: Demonstrates the use of FutureTask */

public class FutureTaskDemo {



    public static void main(String[] args) {

        Task task = new Task();

        FutureTask<Integer> integerFutureTask = new FutureTask<>(task);

        new Thread(integerFutureTask).start();



        try {

            System.out.println("Task running result:"+integerFutureTask.get());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch(ExecutionException e) { e.printStackTrace(); }}}class Task implements Callable<Integer{



    @Override

    public Integer call(a) throws Exception {

        System.out.println("Child thread is calculating");

        int sum = 0;

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

            sum += i;

        }

        returnsum; }}Copy the code

This code creates a Task that implements the Callable interface, passes the Task instance to the FutureTask constructor, and creates an instance of FutureTask. This instance is executed as a Runnable in a new Thread(), and the result is printed using FutureTask’s GET.