FutureTask Source Code Analysis

Keywords: JDK less

FutureTask is an asynchronous task executor that supports cancellation behavior. This class implements the method of Future interface.
Such as:

  1. Cancel Task Execution
  2. Query whether the task is completed
  3. Get the result of task execution ("get" task must be executed to get the result, otherwise it will block until the task is completed.
    Note: Once a task has been completed or cancelled, it cannot be cancelled or restarted. (Unless tasks are run in runAndReset mode from the start)

FutureTask implements Runnable and Future interfaces, so FutureTask can be passed to Thread or Excutor (thread pool) for execution.

If you need to perform time-consuming operations in the current thread, but do not want to block the current thread, you can hand these jobs to FutureTask, and open another thread to complete in the background. When the current thread needs in the future, you can get the calculation results or execution status of the background job through the FutureTask object.

Example

public class FutureTaskDemo {

    public static void main(String[] args) throws InterruptedException{
        FutureTask<Integer> ft = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num = new Random().nextInt(10);
                TimeUnit.SECONDS.sleep(num);
                return num;
            }
        });
        Thread t = new Thread(ft);
        t.start();
        //Here you can do something else, parallel to the futureTask task, and call the get method when you need the results of the futureTask operation.
        try {
            //Wait for the task to complete and get the return value
            Integer num = ft.get();
            System.out.println(num);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

FutureTask Source Code Analysis

JDK 1.7 and before, FutureTask was implemented by inheriting AQS using the internal class Sync.
Shared locks of AQS used internally.
AQS specific implementation can be referred to AbstractQueued Synchronizer Source Code Analysis

JDK 1.8 does not use AQS, but implements a synchronous waiting queue. Before the result returns, all threads are blocked and stored in the waiting queue.

Let's analyze the FutureTask source code for JDK 1.8

FutureTask class structure

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

   /**
     * The running status of the current task.
     *
     * Possible state transitions
     * NEW -> COMPLETING -> NORMAL(With normal results)
     * NEW -> COMPLETING -> EXCEPTIONAL(The result is abnormal.
     * NEW -> CANCELLED(No result)
     * NEW -> INTERRUPTING -> INTERRUPTED(No result)
     */
    private volatile int state;
    private static final int NEW          = 0;  //Initial state
    private static final int COMPLETING   = 1;  //The result is the state between the completion of the calculation or the interruption of the response to the assignment to the return value.
    private static final int NORMAL       = 2;  //The task was completed normally and the result was set.
    private static final int EXCEPTIONAL  = 3;  //Task throw exception
    private static final int CANCELLED    = 4;  //The task has been cancelled
    private static final int INTERRUPTING = 5;  //Thread interrupt status is set ture, but thread does not respond to interrupt
    private static final int INTERRUPTED  = 6;  //Thread has been interrupted

    //Tasks to be performed
    private Callable<V> callable;
    //The result returned for get() may also be an exception thrown by the get() method
    private Object outcome; // non-volatile, protected by state reads/writes
    //To execute the callable thread, call the FutureTask.run() method and set it through CAS
    private volatile Thread runner;
    //The waiting queue of the stack structure, which is the top node in the stack.
    private volatile WaitNode waiters;
    ....

The interface information implemented by FutureTask is as follows:

Runnable Future Interface

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

The Runnable Future interface is based on the Runnable and Future interfaces.

Future interface

public interface Future<V> {
    //Cancellation of tasks
    boolean cancel(boolean mayInterruptIfRunning);
    //Determine whether the task has been cancelled
    boolean isCancelled();
    //Determine whether the task is finished (completed or cancelled)
    boolean isDone();
    //Blocking access to task execution results
    V get() throws InterruptedException, ExecutionException;
    //Supporting timeout for task execution results
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

run method

public void run() {
    //Ensure that the callable task is run only once
    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 {
                //Execution of tasks
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        //Determine whether the task is responding to an interrupt, and if the interrupt is not completed, wait for the interrupt operation to complete
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

1. If the state state state is not New or the running thread runner fails, it returns false directly, indicating that the thread has started, ensuring that the task is executed by only one thread at the same time.
2. Call the callable.call() method. If the call succeeds, execute the set(result) method and set the state to NORMAL. If the call fails to throw an exception, the setException(ex) method is executed, and the state state is set to EXCEPTIONAL, waking up all threads waiting on the get() method.
3. If the current state is INTERRUPTING (step 2 CAS failed), call Thread.yield() until the state is not INTERRUPTING

set method
protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}
  1. Firstly, the NEW state of state is modified to COMPLETING state by CAS.
  2. If the modification is successful, the value of v is assigned to the outcom variable. Then the state state state is changed to NORMAL to indicate that the return value can now be obtained.
  3. Finally, the finishCompletion() method is called to wake up all nodes in the waiting queue.

setException method

protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

Ibid. set (V) method

  1. Firstly, the NEW state of state is modified to COMPLETING state by CAS.
  2. If the modification is successful, the value of v is assigned to the outcom variable. Then the state state is changed to EXCEPTIONAL to indicate that the exception information to be returned is set successfully.
  3. Finally, the finishCompletion() method is called to wake up all nodes in the waiting queue.

HandlePossibleCancellation Interrupt Method

private void handlePossibleCancellationInterrupt(int s) {
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield(); // wait out pending interrupt
}

This method is to wait for the end of the response interrupt (INTERRUPTED) if the response interrupt (EXCEPTIONAL) is being responded to.

finishCompletion Method

private void finishCompletion() {
    for (WaitNode q; (q = waiters) != null;) {
        //By CAS, the top element of the stack is null, which is equivalent to popping up the top element of the stack.
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }
    done();
    callable = null;        // to reduce footprint
}

Pop up the elements in the stack one by one and wake up each node through LockSupport.unpark(t), notifying each thread that the task is completed (either execution completed, cancel, exception, etc.)

runAndReset method (which can be overridden by subclasses and cannot be called directly from outside)

protected boolean runAndReset() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return false;
    boolean ran = false;
    int s = state;
    try {
        Callable<V> c = callable;
        if (c != null && s == NEW) {
            try {
                c.call(); // don't set result
                ran = true;
            } catch (Throwable ex) {
                setException(ex);
            }
        }
    } finally {
        runner = null;
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    return ran && s == NEW;
}

The difference between this method and run method is that the run method can only be run once, while this method can run tasks many times. The runAndReset method does not set the execution result value of the task. If the task is successfully executed, it does not change the state or the NEW state. If the task is cancelled or an exception occurs, it will not be executed again.
It's just after the task is finished.

get method

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

If the state state state is less than or equal to COMPLETING, it indicates that the task has not started or completed, and then calls the awaitDone method to block the calling thread. If the state is larger than COMPLETING, it indicates that the task is completed, or that an exception, interruption, or cancellation occurs. Returns the execution result directly through the report method.

get(long timeout, TimeUnit unit) method

public V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    if (s <= COMPLETING &&
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException();
    return report(s);
}

As with the above get method, this get method supports how long the blocking waits, and throws a TimeoutException exception directly if the timeout occurs.

report method

private V report(int s) throws ExecutionException {
    Object x = outcome;
    if (s == NORMAL)
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

If the state is NORMAL, the task is executed correctly and the calculated value is returned directly.
If the state is greater than or equal to CANCELLED, the task is cancelled successfully, or the response is interrupted, and the CancellationException exception is returned directly.
Otherwise, return the ExecutionException exception.

awaitDone method

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        //If the thread executes the interrupt() method, it removes the node from the queue and throws an exception.
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        int s = state;
        //If the state state state is greater than COMPLETING, the task execution is completed or cancelled.
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        //If state=COMPLETING, yield is used because this state takes a very short time and responds faster through yield than by hanging.
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        //Building Nodes
        else if (q == null)
            q = new WaitNode();
        //Stack the current node
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        //If you need to block the specified time, use LockSupport.parkNanos to block the specified time
        //If the execution is not completed by the specified time, the node is removed from the queue and returned to its current state.
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            //Blocking the current thread
            else
                LockSupport.park(this);
        }
}

Construct the node element of the stack list and inbound the node while blocking the current thread waiting for the thread running the main transaction to wake up the node.
JDK version 1.7 is implemented using AQS bidirectional linked list queues.

removeWaiter method

private void removeWaiter(WaitNode node) {
    if (node != null) {
        node.thread = null;
        retry:
        for (;;) {          // restart on removeWaiter race
            for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                s = q.next;
                if (q.thread != null)
                    pred = q;
                else if (pred != null) {
                    pred.next = s;
                    if (pred.thread == null) // check for race
                        continue retry;
                }
                else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
                    continue retry;
            }
            break;
        }
    }
}

To remove node elements from the stack, CAS spin is needed to ensure successful removal.

cancel method

public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        finishCompletion();
    }
    return true;
}
  1. According to whether mayInterruptIf Running is true or not, CAS is set to INTERRUPTING or CANCELLED. Set up successfully, continue the second step, or return false.
  2. If mayInterruptIfRunning is true, call runner.interupt(), and set the state to INTERRUPTED
  3. Wake up all threads waiting in get() method

Summary: When the state is NEW, cancel and run methods can be run.

  1. After the task starts to run, it can't run again, so it is guaranteed to run only once (except for the runAndReset method).
  2. Tasks can be cancelled in either case if they have not started or have been run but not ended; if they have ended, they cannot be cancelled.

Posted by tthmaz on Sun, 16 Jun 2019 11:38:57 -0700