50 What to Be Cautious About When Using Future Does Future Generate New Threads

50 What to Be Cautious About When Using Future Does Future Generate New Threads #

In this lesson, we will discuss the points to pay attention to when using Future, as well as whether Future creates new threads.

Points to pay attention to when using Future #

1. It is easy to block when obtaining the results of Future in a batch using a for loop. The get method should be called with a timeout limit.

For Future, the first point to note is that it is easy to block when obtaining the results of Future in a batch using a for loop. When calling the get method, a timeout should be used to limit it.

Let’s take a closer look at the situation.

First, let’s assume that there are a total of four tasks that need to be executed. We put them all in the thread pool and obtain them in the order of 1 to 4, that is, by calling the get() method. The code is as follows:

public class FutureDemo {

    public static void main(String[] args) {

        // Create the thread pool

        ExecutorService service = Executors.newFixedThreadPool(10);

        // Submit tasks and use Future to receive the return results

        ArrayList<Future> allFutures = new ArrayList<>();

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

            Future<String> future;

            if (i == 0 || i == 1) {

                future = service.submit(new SlowTask());

            } else {

                future = service.submit(new FastTask());

            }

            allFutures.add(future);

        }

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

            Future<String> future = allFutures.get(i);

            try {

                String result = future.get();

                System.out.println(result);

            } catch (InterruptedException e) {

                e.printStackTrace();

            } catch (ExecutionException e) {

                e.printStackTrace();

            }

        }

        service.shutdown();

    }

    static class SlowTask implements Callable<String> {

        @Override

        public String call() throws Exception {

            Thread.sleep(5000);

            return "Slow task";

        }

    }

    static class FastTask implements Callable<String> {

        @Override

        public String call() throws Exception {

            return "Fast task";

        }

    }

}

As you can see from the code, we create a thread pool and use a list to store 4 Futures. Among them, the first two Futures correspond to slow tasks, which are the SlowTask below the code, and the last two Futures correspond to fast tasks. Slow tasks take 5 seconds to complete, while fast tasks can be completed very quickly, almost no time is spent.

After submitting these 4 tasks, we use a for loop to execute the get method on them one by one to obtain their execution results, and then print out the results.

The execution results are as follows:

Slow task
Slow task
Fast task
Fast task

As you can see, the execution result is 4 lines of output. The first two are slow tasks, and the last two are fast tasks. Although the result is correct, in reality, the program waits for 5 seconds and then quickly prints out these 4 lines of output. img

Here is a problem: the workload of the third task is relatively small, and it can quickly return results. The fourth task will also return results immediately. However, because the first two tasks are slow, when we use the get method to execute, we will get blocked by the first task. In other words, even though the third and fourth tasks have already returned results at this time, we still cannot obtain the results of the third and fourth tasks in a timely manner using this for loop. We can only obtain the result when the first task is completed after 5 seconds, then we can obtain the result of the second task, and then we can get the results of the third and fourth tasks.

Suppose that due to network issues, the first task may not be able to return results for up to 1 minute. At this time, our main thread will be stuck, affecting the efficiency of the program.

In this case, we can use the get method of Future with a timeout parameter, get(long timeout, TimeUnit unit), to solve this problem. The purpose of this method is that if the result cannot be returned within the specified time, a TimeoutException will be thrown. Then we can catch this exception or throw it further, so that we will not be blocked all the time.

2. The lifecycle of Future cannot be reversed.

The lifecycle of Future cannot be reversed. Once the task is completed, it stays permanently in the “completed” state and cannot start over or make a completed Future re-execute the task.

This is the same as the states of threads and thread pools. The states of threads and thread pools cannot be reversed either. The states and transition paths of threads have been covered in Lesson 03, as shown in the following diagram. img

This diagram was used when we explained the states and transition paths. If you have forgotten some of them, you can go back and review the content at that time. In this lesson, I recommend you watch the video because the video clearly illustrates each path, which will make it clearer.

Does Future create new threads? #

Finally, let’s answer this question: Does Future create new threads?

There is a saying that besides inheriting the Thread class and implementing the Runnable interface, there is a third way to create new threads, which is to use Callable and Future, which is called the way to create threads with return values. This statement is incorrect.

In fact, Callable and Future themselves cannot create new threads. They need to rely on other things such as the Thread class or thread pool to execute tasks. For example, after submitting Callable to the thread pool, the actual execution of Callable is done by the threads in the thread pool. The new threads created by the ThreadFactory in the thread pool have nothing to do with Callable and Future, so Future does not create new threads.

That’s all for this lesson. Firstly, we introduced two points to note about Future: the use of timeout limitations when using get; the lifecycle of Future cannot be reversed. Then we explained that Callable and Future are not actually a third way to create new threads.