What are the main functions of 49-Future?

Future 类
The role of Future

The main function of Future is, for example, when doing certain calculations, the calculation process may be time-consuming, sometimes to check the database, or heavy calculations, such as compression, encryption, etc., in this case, if we have been in place Waiting for the method to return is obviously unwise, and the efficiency of the overall program will be greatly reduced. We can put the calculation process in the child thread to execute, and then use Future to control the calculation process executed by the child thread, and finally get the calculation result. In this way, the operating efficiency of the entire program can be improved, which is an asynchronous idea.

The relationship between Callable and Future

Next, we introduce the relationship between Callable and Future. As mentioned earlier, the Callable interface has a major advantage over Runnable in that it can return results. How to obtain this return result? You can use the get method of the Future class to get it. Therefore, Future is equivalent to a memory, which stores the task result of Callable's call method. In addition, we can also use Future's isDone method to determine whether the task has been executed, and also use the cancel method to cancel the task, or obtain the result of the task within a limited time. In short, Future has richer functions. With such a macro concept, let's take a look at the main methods of the Future class in detail.

Future methods and usage

First look at the code of the Future interface. There are 5 methods in total. The code is as follows:

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, TimeoutExceptio
}

Among them, the fifth method is an overload of the fourth method, the method name is the same, but the parameters are different.

get() method: get the result

The main function of the get method is to obtain the result of task execution. The behavior of this method during execution depends on the state of the Callable task. The following 5 situations may occur.

(1) The most common thing is that when the get is executed, the task has been executed, and you can return immediately to get the result of the task execution.

(2) The task has no result yet. This is possible. For example, if we put a task in the thread pool, there may be a lot of tasks backlogged in the thread pool. When it is not my turn to execute, I go to get, here In this case, it is equivalent to the task has not yet started; in another case, the task is being executed, but the execution process is relatively long, so when I go to get, it is still in the process of execution. 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 then the result will be returned.

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

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

(5) The task is timed out. We know that the get method has an overload method, that is with delayed parameters. After calling the get method with delayed parameters, if the call method successfully completes the task within the specified time, then the get will Normal return; but if the task is not completed after the specified time, the get method will throw a TimeoutException, which means it has timed out.

The following uses a diagram to make the process clearer:
Insert picture description here
In the diagram, on the right is a thread pool, and there are some threads in the thread pool to perform tasks. Focus on the left side of the figure, you can see that there is a submit method that submits a Task to the thread pool. This Task implements the Callable interface. When we submit this task to the thread pool, calling the submit method will immediately Return a Future type object. The current content of this object is empty, and it does not contain the calculation result, because the calculation has not been completed yet.

When the calculation is completed, that is, when we can get the result, the thread pool will fill the result into the previously returned Future (that is, the f object) instead of creating a new Future at this time. At this time, you can use Future's get method to get the execution result of the task.

Let's look at a code example:

/**
 * 描述:     演示一个 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() throws Exception {
    
    
            Thread.sleep(3000);
            return new Random().nextInt();
        }
    }
}

In this code, the main method creates a thread pool of 10 threads, and uses the submit method to submit a task. This task is shown at the bottom of the code. It implements the Callable interface. What it does is sleep for three seconds and then return a random number. Next, we will print out the result of future.get directly, and the result will be a random number, such as 100192, etc., which is normally printed out. This code corresponds to the explanation we have just shown, which is also the most commonly used usage of Future.

isDone() method: determine whether the execution is complete

Let's take a look at some other methods of Future, such as the isDone() method, which is used to determine whether the current task has been executed.

It should be noted that if this method returns true, it means the execution is complete; if it returns false, it means it has not been completed. But if true is returned here, it does not mean that the task was successfully executed, for example, an exception was thrown in the middle of the task execution. So in this case, for this isDone method, it will actually return true, because for it, although an exception has occurred, the task will not be executed in the future, and it has indeed been executed. It's over. Therefore, when the isDone method returns true, it does not mean that the task was successfully executed, but only that it has been executed.

Let's take a look at a code example, 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, you can see that there is a thread pool, and to submit a task to the thread pool, this task will directly throw an exception. Then we will use a for loop to sleep, and let it slowly print out the 5 numbers 0 ~ 4, the purpose of this is to play a certain delay. After this is done, call the isDone() method, print the result, and then call future.get().

The execution result of this code is like this:

0
1
2
3
4
true
java.util.concurrent.ExecutionException: java.lang.IllegalArgumentException: Callable抛出异常
...

**Note here,** we know that this exception is actually thrown when the task is just executed, because there is no other logic in our computing task, only exceptions are thrown. Let's look at it again. When did the console print out an exception? It prints out the exception information after the true printing is completed, that is, the exception is printed when the get method is called.

**This code proves three things: **The first thing, even if the task throws an exception, the isDone method will still return true; the second thing, although the thrown exception is IllegalArgumentException, for get, The exception that it throws is still ExecutionException; the third thing, although the exception is thrown at the beginning of the task execution, it really has to wait until we execute the get to see the exception.

cancel method: cancel the execution of 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 situations:

The first case is the simplest, that is, when the task has not started execution, once cancel is called, the task will be cancelled normally and will not be executed in the future, then the cancel method returns true.

The second case is also relatively simple. If the task has been completed, or it has been canceled before, the cancel method is executed to indicate that the cancellation failed, and false is returned. Because the task, whether it has been completed or has been canceled, can no longer be canceled.

The third situation is special, that is, the task is being executed. At this time, executing the cancel method will not directly cancel the task, but will make judgments based on the parameters we pass in. The cancel method must pass in a parameter, which is called mayInterruptIfRunning . What does it mean? If the parameter passed in is true, the thread executing the task will receive an interrupt signal. The task being executed may have some logic to handle the interrupt and then stop. This is easier to understand. If false is passed in, it means that the running task will not be interrupted, that is to say, the cancel will not have any effect this time, and the cancel method will return false.

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

Passing in true is applicable when you know that this task can handle interrupts.

What is the application of false when it is passed in?

  • If we clearly know that this thread cannot handle interrupts, we should pass in false.
  • We don’t know whether this task supports cancellation (whether it can respond to interrupts), because in most cases the code is multi-person collaboration, we may not be fully sure whether this task supports interrupts, so in this case, too Should be passed in false.
  • If this task starts to run, we hope that it will be completely executed. In this case, false should also be passed in.

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

isCancelled() method: determine whether it is cancelled

The last method is the isCancelled method, which determines whether it is cancelled. It is used in conjunction with the cancel method, which is relatively simple.

The above is an introduction to the main methods of Future.

Use 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 get the result of the Future class and task.

FutureTask is a task first, and then has the semantics of the Future interface, because it can get the result of execution in the future (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 is the two interfaces extends Runnable and Future, and their relationship is shown in the following figure:
Insert picture description here

Since RunnableFuture inherits the Runnable interface and the Future interface, and FutureTask implements the RunnableFuture interface, FutureTask can be executed by a thread as a Runnable, and it can also be used as a Future to get the return value of Callable.

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

Let's use the code to demonstrate:

/**
 * 描述:     演示 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运行结果:"+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("子线程正在计算");
        int sum = 0;
        for (int i = 0; i < 100; i++) {
    
    
            sum += i;
        }
        return sum;
    }
}

As can be seen in this code, a Task that implements the Callable interface is first created, then the Task instance is passed into the FutureTask constructor, a FutureTask instance is created, and this instance is put as a Runnable Go to new Thread() to execute, and finally use FutureTask's get to get the result and print it out.

The execution result is 4950, which is the result of 0+1+2+…+99 in the task.

to sum up

We first understand the role of Future at a macro level, and then understand the relationship between Callable and Future, and then give a detailed introduction to the various methods of Future, and finally give the FutureTask method to create the usage of Future.

Guess you like

Origin blog.csdn.net/Rinvay_Cui/article/details/111056535