Exploration of Future and FutureTask in Java

When will Future be used?

When the execution of a program needs to depend on the execution of another thread or the calculation result, the thread needs to block and wait for the execution of another thread. Future's get() method blocks the current thread until another thread has finished executing and returns the result.

What is Future?

Future is an interface that provides some method definitions for controlling the execution of tasks and obtaining execution status and results. The source code is as follows:

public interface Future<V> {

	/**
    * Cancel execution
    * Parameter denotes whether canceling the task being performed is allowed
    */
    boolean cancel(boolean mayInterruptIfRunning);
    
    /**
    * Has execution been cancelled?
    */
    boolean isCancelled();
    
     /**
    * Has the execution been completed?
    */
    boolean isDone();
    
      /**
    * Get execution results (blocking)
    */
    V get() throws InterruptedException, ExecutionException;

	 /**
    * Get the execution result after a specific time, and return null if the execution is not completed.
    */
    V get(long timeout, TimeUnit unit)  throws InterruptedException, ExecutionException, TimeoutException;
    
}

Getting the results of worker thread execution through Future
public static void main(String[] args) {

        System.out.println("The main thread begins to execute...");
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<Integer> future = executorService.submit(new Task());
        try {
            Integer integer = future.get();
            System.out.println("Get the results of worker thread execution:" + integer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("The main thread has finished executing...");
        
 }

static class Task implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("Work threads begin to execute...");
            int value = 0;
            for (int i = 0; i < 5; i++) {
                value++;
                System.out.println(value);
                Thread.sleep(1000);
            }
            System.out.println("The worker thread has finished executing...");
            return value;
        }
    }

Perform printing results:

The main thread begins to execute...
Work threads begin to execute...
1
2
3
4
5
 The worker thread has finished executing...
Get the results of workthread execution:5
 The main thread has finished executing...

In the above example
The submit() method of thread pool is used to start the execution of worker thread task. In fact, there are some overloaded methods in this method. It can not only pass Callable to Runnable, but Callable can directly determine the data type returned by generics. Runnable is a little troublesome. From the following overloading method, either directly into Runnable, or call the overloading method of two parameters to give the worker thread a fixed return result, but either way can not get the result of the program calculation in the run() method, because the run() method has no return value. Callable's call() method can return the results of the program.

public interface Runnable {
    public abstract void run();
}

public interface Callable<V> {
    V call() throws Exception;
}
public abstract class AbstractExecutorService implements ExecutorService {

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
}
What is FutureTask

FutureTask can be seen from the class name as encapsulation of work tasks, and with Future, it should also contain some features of Future. It has two constructive methods: Callable can be passed in, Runnable can be passed in, and Runnable can eventually be encapsulated as Callable:

public class FutureTask<V> implements RunnableFuture<V> {
 	public FutureTask(Callable<V> callable) {
     	   if (callable == null)
      	      throw new NullPointerException();
    	    this.callable = callable;
    	    this.state = NEW;       // ensure visibility of callable
  	 }
  	 
   	 public FutureTask(Runnable runnable, V result) {
     	   this.callable = Executors.callable(runnable, result);
     	   this.state = NEW;       // ensure visibility of callable
     }
     
	......
	
	public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    
    ......
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

FutureTask is the implementation class of RunnableFuture. Look at the definition of RunnableFuture. RunnableFuture interface inherits both Runnable and Future. So FutureTask is essentially an extension of Runnable interface and a further encapsulation of Runnable or Callable objects passed in by construction method, which adds tasks to execution. Control and status record.

Since it is used as Runnable, its task invocation logic must be in its own implementation of run(). From the above source code, we can see that the inner part of run() method is actually implemented through Callable's call method, so as to achieve the purpose of obtaining the return value.

Getting the results of worker thread execution through FutureTask

For the above example, let's take a look at getting its execution results through FutureTask

 public static void main(String[] args) {
 
        System.out.println("The main thread begins to execute...");
        ExecutorService executorService = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executorService.submit(futureTask);
        
        try {
            Integer integer = futureTask.get();
            System.out.println("Get the result of the subthread execution:" + integer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("The main thread has finished executing...");
        
 }

Essentially, FutureTask is considered as submit() method that Runnable passes to ExecutorService interface. As mentioned earlier, submit() method has three different overload methods, so see what difference they have. The concrete implementation of submit() is in abstract class AbstractExecutorService:

public abstract class AbstractExecutorService implements ExecutorService {

	 public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

	 protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }
}

The three overloaded submit() methods are implemented. Whether the incoming Runnable or Callable is encapsulated as FutureTask, it is finally executed by ExecutorService.execute. Therefore, it is essentially through FutureTask to obtain the execution results of worker threads through FutureTask.

Posted by savagenoob on Wed, 31 Jul 2019 10:34:18 -0700