FutureTask class source code focus
- FutureTask is a cancelable asynchronous task. It implements the Runnable interface and rewrites the run() method, so its task is executed in the thread and can be used in conjunction with the thread pool. For the Runnable source code, see my article Runnable
- FutureTask implements the Future interface, which naturally has the meaning of its interface. You can see the Future source code in my article Future
- FutureTask can be roughly divided into three operations: executing a task, obtaining a task execution result, and canceling a task
- FutureTask has a waiting list with WaitNode as its node. There is only one thread in the node. When obtaining the task execution result, the thread that wants to obtain the result is placed in the node and blocked, and stored in the linked list composed of WaitNode, because the task may not be completed when get ting the result
Implementation of FutureTask class
FutureTask class properties
- state is the status of FutureTask. Its values include new, completing, normal, exception, canceled, interrupting, and interrupted. state indicates the status of FutureTask task execution
- Callable is a specific task executed by FutureTask. The callable interface is a functional interface. It only defines a call() method with return value, which is an improvement of Runnable. You can see my article for the callable source code Callable
- outcome is an Object used to store the results of task execution or the exception Object Throwable caused by exceptions in the execution process
- runner is the thread object that executes the FutureTask task
- waiters is the chain header node of the waiting thread,
- STATE, RUNNER and WAITERS are variable handles. Variable handles are used to point to CAS or Atomic operations on variables instead of Atomic classes. Why does JDK9 flood into variable handles? In the past, if we wanted to use CAS or Atomic operations, we could only define our variables as AtomicInteger and AtomicLong, Now, with the introduction of variable handles, we can still define our variables as usual, but we can still perform CAS and Atomic operations
public class FutureTask<V> implements RunnableFuture<V> { private volatile int state; private static final int NEW = 0; private static final int COMPLETING = 1; private static final int NORMAL = 2; private static final int EXCEPTIONAL = 3; private static final int CANCELLED = 4; private static final int INTERRUPTING = 5; private static final int INTERRUPTED = 6; private Callable<V> callable; private Object outcome; private volatile Thread runner; private volatile WaitNode waiters; private static final VarHandle STATE; private static final VarHandle RUNNER; private static final VarHandle WAITERS; }
Linked list node of waiting thread
You can see that there is a thread inside each waiting node
static final class WaitNode { volatile Thread thread; volatile WaitNode next; WaitNode() { thread = Thread.currentThread(); } }
Perform tasks
run operation
- It can be seen that only when the state is NEW, and the variable handle runner is used to perform CAS operation on the variable runner, and the thread runner executing the task is set as the current thread successfully, will the subsequent operation be executed. If one of these two conditions is not met, it will be returned directly, which can prevent multiple threads from repeatedly executing the same task
- The execution task is to take out the Callable and execute its call() method to obtain the execution result. If no exception occurs, call the set method to set the execution result. If an exception occurs, call the setException method to set the returned result as the exception object
- finally, the final block should set the runner, that is, the thread executing the task, to be empty, and judge whether the current state state is interrupted or interrupted. If so, call handlePossibleCancellationInterrupt. Because the interrupt signal is not received until the task is almost completed, call Thread.yield() to complete the task as soon as possible to prevent interruption
- Finally, FutureTask implements Runnable, so the run method is actually executed in a thread
public void run() { // Prevent multiple threads from performing the same task if (state != NEW || !RUNNER.compareAndSet(this, 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 = null; int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
set operation
- Use CAS to try to set the state to the completed state. If successful, assign the execution result of the above call, that is, the incoming v, to the outcome, which is used to accommodate the execution result or the exception object in execution
- setRelease will set the state value to the NORMAL normal state. setRelease can ensure that the read and write after and before accessing the state variable will not be reordered. Read is to read the variable from main memory, and write is to brush the data in the write buffer to main memory
- Finally, we call the finishCompletion method to empty the header node of the waiting node chain, and empty the threads of all nodes on the waiting list, and wake up the blocking thread. waiters
protected void set(V v) { if (STATE.compareAndSet(this, NEW, COMPLETING)) { outcome = v; STATE.setRelease(this, NORMAL); finishCompletion(); } }
Finishcompletement operation
- If the waiters waiting node is not empty, enter the loop. First use CAS to set the waiters to empty. If successful, carry out subsequent operations. If unsuccessful, spin in the outer for loop until the operation is successful
- After CAS succeeds, set the internal thread of q to empty, call LockSupport.unpark to contact the blocking of the thread, keep moving behind the q linked list, release each waiting node and wake up its internal thread, and then jump out of the loop. The first break jumps out of the memory loop. Although the second break is in the if statement, it can still jump out of the first layer loop
- Finally, execute the done() operation. The done operation is the method to be overridden by the subclass, which is empty by default. It is used to perform the operation of a callback method after the completion of a similar task, and then set the call, that is, the task object of the FutureTask, to null
- Why do we need these waiting nodes? Because when runner threads execute the task, there are other threads calling get() to get the results of the task execution. At this point, each thread will create a WaitNode and block it in the waiters list until finishCompletion is done after what runner has done.
private void finishCompletion() { for (WaitNode q; (q = waiters) != null;) { if (WAITERS.weakCompareAndSet(this, 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; q = next; } break; } } done(); callable = null; }
setException operation
You can see that as like as two peas set methods, they are almost the same as the above method, but only setRelease is used to set state to EXCEPTIONAL abnormal state.
protected void setException(Throwable t) { if (STATE.compareAndSet(this, NEW, COMPLETING)) { outcome = t; STATE.setRelease(this, EXCEPTIONAL); finishCompletion(); } }
Handlepossibleconcellationinterrupt operation
Thread.yield() is a prompt sent to the scheduler, indicating that the current thread is willing to use the processor. The scheduler can ignore this prompt at will
private void handlePossibleCancellationInterrupt(int s) { if (s == INTERRUPTING) while (state == INTERRUPTING) Thread.yield(); }
runAndReset operation
You can see that as like as two peas in run operation, you do not implement set method, and set method works as follows
- Assign the return value returned by the call method of Callable to outcome
- Set state to COMPLETING and then NORMAL
- Empty the waiters, traverse the waiting list, empty the threads of all nodes, and unlock the blocking threads
Since no set operation is performed to change the state, the runAndReset() method can be executed multiple times. runAndReset() is designed to essentially execute multiple tasks
protected boolean runAndReset() { if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread())) return false; boolean ran = false; int s = state; try { Callable<V> c = callable; if (c != null && s == NEW) { try { c.call(); ran = true; } catch (Throwable ex) { setException(ex); } } } finally { runner = null; s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } return ran && s == NEW; }
Get the result of task execution
get operation
- Get the current task execution state. If the state is NEW or COMPLETING, execute the awaitDone method to block and wait for the task execution to complete. COMPLETING is not the final state of COMPLETING the task normally, and NORMAL is the final state
- Then call report to return the result of the completed task or cause an exception.
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); }
awaitDone operation
- startTime is the task start time, q is a waiting node containing the current thread, queued is whether the Q node is queued on the waiting node list, and queued is true, indicating that the Q node is queued on the waiting node list
- for loop spin, first obtain the state. If the state is greater than COMPLETING, it may be normal, exception, canceled, interrupting, and interrupted. These states should wait directly, set the thread of p to null, and return to the current state
- If the current state is equal to COMPLETING, it means that it is in the intermediate state of NORMAL and the task is almost finished. Then call Thread.yield() to send a prompt to the scheduler, indicating that the current thread is willing to use the processor to speed up the task execution (the scheduler can ignore this prompt at will)
- If the Thread.interrupted method is used to detect that the current thread is marked with an interrupt flag, remove the waiting node p from the waiting list and throw an InterruptedException interrupt exception to interrupt the program
- If q equals null, first judge whether the function has set timeout return and timeout, and then create the WaitNode node p traversed internally by the current thread
- If node q is not queued in the waiters waiting list, use CAS to set the waiters to Q. before that, point q.next to the waiters, which is equivalent to inserting node Q into the queue head, and then use CAS to set the queue head to position Q
- If the function sets the timeout return, enter the area to judge the timeout return. When entering for the first time, the startTime is 0, then set the startTime as the current nanosecond time of the system, and the parkNanos blocking time as the nanos passed in by the function. Then directly judge whether the state is less than COMPLETING, that is, when it is in the new state, use LockSupport.parkNanos(this, parkNanos) Method blocks the current thread parkNanos nanoseconds
- When entering this timeout judgment block for the second time or later, calculate the difference between the current nanosecond time and the start nanosecond time. If it is greater than the given timeout time, nanos, directly remove the q node in the waiting list, and then return to the status. If it is not greater than, calculate the remaining blocking time nanos - elapsed, and continue to call LockSupport.parkNanos to block the current thread
- There are two points to note above. Why can parkNanos be changed in two places when it is defined as final? This is a for loop, and those two places are the results of if and else. The for loop will be redefined each time it re enters, while if and else will only execute one of them each time
- Another thing to note is that LockSupport.parkNanos blocks the current thread. How does the task execute? This shows that the thread calling the get() method is different from the thread executing the task. The thread executing the task is the runner, while the thread calling the get method is other threads. It will block in the waiting list before the runner finishes executing the task, Until runner executes the task, call finishCompletion to wake them up and get the results.
- For the last else, if none of the previous else is satisfied, that is, the unsatisfied status is greater than or equal to COMPLETING, the current thread is marked with interrupt flag, the unsatisfied thread is not queued, and the set timeout time is not satisfied, that is, when the task status is NEW and the thread is not marked with interrupt flag and queued in the waiting list, the thread without timeout will be blocked by calling LockSupport.park, Until runner executes the task, call finishCompletion to wake them up and get the results.
private int awaitDone(boolean timed, long nanos) throws InterruptedException { long startTime = 0L; WaitNode q = null; boolean queued = false; for (;;) { int s = state; if (s > COMPLETING) { if (q != null) q.thread = null; return s; } else if (s == COMPLETING) Thread.yield(); else if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } else if (q == null) { if (timed && nanos <= 0L) return s; q = new WaitNode(); } else if (!queued) queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q); else if (timed) { final long parkNanos; if (startTime == 0L) { startTime = System.nanoTime(); if (startTime == 0L) startTime = 1L; parkNanos = nanos; } else { long elapsed = System.nanoTime() - startTime; if (elapsed >= nanos) { removeWaiter(q); return state; } parkNanos = nanos - elapsed; } if (state < COMPLETING) LockSupport.parkNanos(this, parkNanos); } else LockSupport.park(this); } }
report operation
- s is the task execution status passed in above. If the task execution status is NORMAL, the execution result outcome will be returned
- If s is greater than or equal to CANCELLED, indicating s > = 4, then s may be in the three states of CANCELLED, INTERRUPTING and INTERRUPTED, and a CancellationException will be thrown
- If s is EXCEPTIONAL, that is, s is equal to 3, the outcome is a Throwable object, which is passed into ExecutionException and an ExecutionException is thrown
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); }
Get (long timeout, timeunit) operation
It can be seen that it is not different from get. It is just judged that when awaitDone method returns, if the status is still less than or equal to COMPLETING, and the task is not completed, a timeout exception will be thrown
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); }
Cancel task
cancel operation
- The first if statement is a little complicated. We can ignore the external non sign first!, It means that the state is NEW and CAS is used to set the value of state successfully, while the ternary operator in it only determines what state to set. Adding a non sign means that if the state is not NEW or CAS is used to set the state to other states unsuccessfully, it directly returns false, which means to cancel the failure
- Only when the state is NEW and CAS is used to set the state to INTERRUPTING or canceled, can subsequent operations be performed. This is to prevent concurrent cancellation of multiple threads. If only one thread can CAS successfully, the thread can perform subsequent operations
- mayInterruptIfRunning means interrupt. If it is set to true, CAS will be used to set the state to the INTERRUPTING state
- At try, if mayInterruptIfRunning is true, call the t.interrupt method of the thread, mark the interrupt flag, and call setRelease to set the state to INTERRUPTED interrupted
- Whether it is directly cancelled or interrupted, it will execute finishcompletement, empty the waiting list, and wake up all threads waiting for results
public boolean cancel(boolean mayInterruptIfRunning) { if (!(state == NEW && STATE.compareAndSet (this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; try { if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null) t.interrupt(); } finally { STATE.setRelease(this, INTERRUPTED); } } } finally { finishCompletion(); } return true; }