49 What Are the Main Functions of Future

49 What Are the Main Functions of Future #

In this lesson, we will explain the main functions of Future.

Future class #

The purpose of Future #

The main purpose of Future is that when we have a certain operation that may take a long time, such as querying a database or performing heavy calculations like compression or encryption, it is not wise to wait for the method to return. This will greatly reduce the efficiency of the overall program. We can execute the operation in a separate thread and control the calculation process through Future, and finally obtain the calculation result. This can improve the overall efficiency of the program, which is an asynchronous approach.

The relationship between Callable and Future #

Next, let’s talk about the relationship between Callable and Future. As we mentioned before, the main advantage of the Callable interface compared to the Runnable is that it can have a return result. How can we get this return result? We can use the get method of the Future class to obtain it. Therefore, Future functions as a storage that stores the task result of the Callable’s call method. In addition, we can also use the isDone method of Future to determine whether the task has been completed, cancel the task with the cancel method, or obtain the task result with a timeout. In short, Future has rich functionality. With this macro concept in mind, let’s take a closer look at the main methods of the Future class.

Methods and usage of Future #

Let’s start with the code of the Future interface, which has a total of 5 methods, as shown below:

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Among them, the 5th method is an overload of the 4th method. The method name is the same, but the parameters are different.

get() method: Obtain the result #

The main purpose of the get method is to obtain the result of the task execution. The behavior of this method depends on the status of the Callable task during execution. There may be 5 different situations:

(1) The most common situation is when the task has already been completed when using get. In this case, the result can be returned immediately.

(2) The task has not yet produced a result. This is possible when we put a task into the thread pool, and the thread pool may have a backlog of many tasks. When we call get before it is my turn to execute, it means that the task has not yet started. Another situation is that the task is still in progress but the execution process is taking a long time. When we call get at this time, it is still in the execution process. Regardless of whether the task has not started or is in progress, when we call get, the current thread will be blocked until the task is completed and the result is returned.

(3) An exception occurred during task execution. Once this happens, when we call get again, it will throw an ExecutionException exception. Regardless of the type of exception thrown in the call method, the exception obtained when executing the get method will always be ExecutionException.

(4) The task was canceled. If the task is canceled and we try to get the result using the get method, it will throw a CancellationException.

(5) Timeout. We know that the get method has an overloaded method with a delay parameter. After calling this get method with a delay parameter, if the call method completes the task within the specified time, get will return normally. But if the specified time is reached and the task is not completed yet, the get method will throw a TimeoutException, indicating that it has timed out.

The process is illustrated in the following diagram:

img

In the diagram, there is a thread pool on the right side. The thread pool has some threads to execute tasks. The focus is on the left side of the diagram, where you can see a submit method. This method submits a Task to the thread pool. This Task implements the Callable interface. When we submit this task to the thread pool, the submit method will immediately return an object of type Future, which currently contains no content and does not include the calculation result because the calculation has not been completed yet.

Once the calculation is completed, i.e., when we can obtain the result, the thread pool will fill in the previously returned Future (the f object) with this result, instead of creating a new Future at this time. At this point, we can use the get method of Future to obtain the execution result of the task.

Let’s take a look at a code example:

/**
 * Description: Demonstrates how to use 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) {
            // Handle interrupted exception
        }
    }
}
                e.printStackTrace();
    
            }
    
        }
    
    }

In this code snippet, the main method creates a thread pool with 10 threads, and submits a task using the submit method. The task is defined at the bottom of the code, implementing the Callable interface. The task sleeps for three seconds and then returns a random number. Next, we directly print the result of the future.get method, which prints a random number, for example, 100192. This code corresponds to the explanation of our previous diagram and represents the most common use case of Future.

isDone() Method: Check if the task is completed #

Now let’s take a look at some other methods of Future, such as the isDone() method, which is used to check if the task has completed.

Note that this method returns true only if the task has completed; it returns false if the task is still running. However, if the method returns true, it does not mean that the task executed successfully. For example, if an exception is thrown halfway through the task execution, the isDone method will still return true. This is because, to the isDone method, even though an exception occurred, the task will not be executed again in the future, so it is considered complete. Therefore, when the isDone method returns true, it does not necessarily indicate a successful execution of the task, only that it has completed.

Let’s take a look at an example code to demonstrate this. The code is as follows:

    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() throws Exception {
    
                throw new IllegalArgumentException("Callable抛出异常");
    
            }
    
        }
    
    }
}
}

}

In this code, we can see that there is a thread pool and tasks are submitted to the thread pool. These tasks throw exceptions directly. Next, we use a for loop to sleep and gradually print the numbers 0 to 4. The purpose of this is to introduce a delay. After this is completed, we call the isDone() method and print the result, and then call future.get().

The result of executing this code is as follows:

0

1

2

3

4

true

java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Callable throws an exception

...

Please note that we know that this exception is actually thrown when the task is just executed because our calculation task has no other logic, only throwing an exception. Let’s take a look at when the console prints the exception. It prints the exception information after printing “true”, which means that the exception is printed when get() is called.

This code demonstrates three things: Firstly, even if the task throws an exception, the isDone() method still returns true. Secondly, although the exception thrown is IllegalArgumentException, for get(), the exception it throws is still ExecutionException. Thirdly, although the exception was thrown at the beginning of the task execution, we only see the exception when we call get().

cancel() method: Cancelling task execution #

Next, let’s take a look at the cancel() method. If you don’t want to execute a certain task, you can use the cancel() method, which has the following three scenarios:

The first scenario is the simplest, which is when the task has not started executing. Once cancel() is called, the task will be cancelled normally and will not be executed in the future, so cancel() will return true.

The second scenario is also relatively simple. If the task has already completed or has been cancelled before, executing cancel() will indicate the cancellation failed and return false. This is because tasks cannot be cancelled once they have completed or have been cancelled before.

The third scenario is more special. If the task is currently executing, calling cancel() will not directly cancel the task, but will depend on the parameter we passed in. The cancel() method must pass in a parameter called mayInterruptIfRunning, what does it mean? If we pass in true, the thread executing the task will receive an interrupt signal, and the running task may have some interrupt handling logic, thus stopping. This is easier to understand. If false is passed in, it means that the running task will not be interrupted, that is, this cancel will have no effect, and the cancel() method will return false.

So how do we choose whether to pass in true or false?

Passing in true is suitable when we are sure that the task can handle interrupts.

When is false passed in?

  • If we are certain that the thread cannot handle interruptions, false should be passed in.
  • We don’t know if this task supports cancellation (whether it can respond to interrupts), because in most cases, the code is collaborative among multiple people, and we may not have full confidence in whether this task supports interruption. In this case, false should also be passed in.
  • If we want the task to complete fully once it starts running. In this case, false should also be passed in.

These are the different meanings and selection methods for passing in true and false.

isCancelled() method: Checking if the task has been cancelled #

The last method is isCancelled(), which is used in conjunction with the cancel() method. It is relatively simple.

These are the main methods of Future.

Using FutureTask to create Future #

In addition to using the submit() method of the thread pool to return a future object, you can also use FutureTask to obtain the Future class and the result of the task.

FutureTask is first a task, and then it has the semantics of the Future interface, because it can obtain the execution result in the future.

Let’s take a look at the code implementation of FutureTask:

public class FutureTask<V> implements RunnableFuture<V>{

 ...

}

As you can see, it implements an interface called RunnableFuture. Let’s take a look at the code implementation of the RunnableFuture interface:

public interface RunnableFuture<V> extends Runnable, Future<V> {

    void run();

}

It can be seen that it extends the Runnable and Future interfaces, and their relationship is shown in the following diagram:

img

Since RunnableFuture extends the Runnable interface and Future interface, and FutureTask implements the RunnableFuture interface, FutureTask can be both executed as a Runnable by a thread and obtained as a return value by a Callable.

The typical usage is to pass the Callable instance as the parameter of the FutureTask constructor to generate a FutureTask object, then treat this object as a Runnable object, and execute it in a thread pool or a new thread, and finally, you can get the result of the task execution through FutureTask.

Let’s demonstrate it with code:

/**
 * Description: demostrates the usage 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("Result of the task: " + integerFutureTask.get());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

}

class Task implements Callable<Integer> {

    @Override

    public Integer call() throws Exception {

        System.out.println("Sub-thread is calculating");

        int sum = 0;

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

            sum += i;

        }

        return sum;

    }

}

In this code, a Task that implements the Callable interface is created first, then this Task instance is passed to the constructor of FutureTask, creating a FutureTask instance. This instance is treated as a Runnable object and executed by putting it in new Thread(), and finally, the result is obtained through the get() method of FutureTask and printed.

The result of executing this code is 4950, which is the value of 0+1+2+…+99 in the task.

Summary #

Finally, let’s summarize this lesson. In this lesson, we first explain the macro role of Future, then explain the relationship between Callable and Future, and then provide a detailed introduction to the various methods of Future. Finally, we introduce the usage of the FutureTask method to create Future.