How does Java implement the Future pattern? Detailed explanation of ten thousand words!

JDK1.8 source code analysis project (Chinese note) Github address:

https://github.com/yuanmabiji/jdk1.8-sourcecode-blogs

1 what is future?

For example, when we buy things online, an order number will be generated after placing an order, and then the merchant will deliver goods according to the order number, and then there will be a delivery order number after the delivery, and then the express company will express the online shopping to us according to the delivery order number. In this process, this series of orders are important documents for us to receive goods.

Therefore, the future of JDK is similar to the order number of our online shopping. When we perform a time-consuming task, we can start another thread to perform the time-consuming task asynchronously, and we can do other things at the same time. When the task is finished, we can extract the execution result of the time-consuming task according to the future "order number". So future is also an application mode in multithreading.

Extension: when it comes to multithreading, what's the difference between Future and Thread? The most important difference is that Thread does not return results, while Future mode does.

2 how to use Future

Now that we know what Future is, let's take a simple example to see how to use it.

If we want to make hot pot now, we should first prepare two things: Boil the water and prepare the ingredients. Because boiling water is a relatively long process (equivalent to time-consuming business logic), we can prepare hotpot ingredients (main thread) while boiling water (equivalent to another thread), and then we can start to fight hotpot when both are ready.

// DaHuoGuo.java

public class DaHuoGuo {
	public static void main(String[] args) throws Exception {
		FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
			@Override
			public String call() throws Exception {
				System.out.println(Thread.currentThread().getName() + ":" + "Start boiling water...");
				// Time consumption of simulated boiling water
				Thread.sleep(2000);
				System.out.println(Thread.currentThread().getName() + ":"  + "The boiled water is done...");
				return "boiling water";
			}
		});

		Thread thread = new Thread(futureTask);
		thread.start();

		// do other thing
		System.out.println(Thread.currentThread().getName() + ":"  + " A thread execution is started future At this time, we can do something else (such as preparing hotpot ingredients)...");
		// Time consuming for simulation preparation of hotpot ingredients
		Thread.sleep(3000);
		System.out.println(Thread.currentThread().getName() + ":"  + "Hotpot ingredients are ready");
		String shicai = "Hotpot ingredients";

		// The boiled water is a little better. We have the boiled water
		String boilWater = futureTask.get();

		System.out.println(Thread.currentThread().getName() + ":"  + boilWater + "and" + shicai + "We're ready. We can start the hot pot");
	}
}

The execution result is as follows, which is in line with our expectation:

As can be seen from the above code, we use Future mainly in the following steps:

  1. Create a new Callable anonymous function implementation class object. Our business logic is implemented in the Callable call method. The generic type of Callable is the return result type;
  2. Then the Callable anonymous function object is passed in as the construction parameter of futuretask to build a futuretask object;
  3. Then, the futureTask object is passed in as a Thread construct parameter and the Thread is started to execute the business logic;
  4. Finally, we call the get method of the futureTask object to get the business logic execution result.

You can see that the JDK classes related to Future use mainly include FutureTask and Callable. The following mainly analyzes the source code of FutureTask.

Extension: another way to use Future is to submit the Callable implementation class to the thread pool for execution, which will not be introduced here, just Baidu.

3. Analysis of futuretask class structure

Let's first look at the class structure of FutureTask:

You can see that FutureTask implements the RunnableFuture interface, which in turn inherits the Future and Runnable interfaces. Because FutureTask indirectly implements the Runnable interface, it can be executed as a task by Thread. In addition, the most important point is that FutureTask also indirectly implements the Future interface, so it can also obtain the results of task execution. Let's take a brief look at the api related to these interfaces.

// Runnable.java

@FunctionalInterface
public interface Runnable {
    // Execute thread task
    public abstract void run();
}

Runnable has nothing to say. I'm sure everyone is familiar with it.

// Future.java

public interface Future<V> {
    /**
     * Attempts to cancel the execution of thread tasks are divided into the following situations:
     * 1)If the thread task has been completed or cancelled or cannot be cancelled for other reasons, it will fail and return false;
     * 2)If the task has not yet started execution and cancel method is executed, the task will be cancelled execution and true will be returned. Which state does TODO correspond to at this time??? Don't you understand?
     * 3)If the task has already started executing, the mayInterruptIfRunning parameter determines whether to cancel the execution of the task.
     *    It is worth noting that the essence of cancel(true) is not to cancel the execution of thread tasks, but to issue a thread
     *    Interrupt signal, generally need to be combined Thread.currentThread().isInterrupted() to use.
     */
    boolean cancel(boolean mayInterruptIfRunning);
    /**
     * Judge whether the task is cancelled. If it is cancelled before the task is completed, true will be returned
     */
    boolean isCancelled();
    /**
     * This method always returns true no matter whether the task stops normally, the exception or the task is cancelled.
     */
    boolean isDone();
    /**
     * Get the task execution result. Note that the block waits for the task execution result.
     */
    V get() throws InterruptedException, ExecutionException;
    /**
     * Get the task execution result. Note that the block waits for the task execution result.
     * If no result is obtained within the specified time, a timeout exception will be thrown
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

The future interface symbolizes the result of asynchronous task execution, that is, a time-consuming task can be executed by another thread, and then we can do other things, finish other things, and then we can call Future.get() method can get the result. If the asynchronous task is not finished, it will block waiting until the asynchronous task finishes executing to get the result.

// RunnableFuture.java

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

RunnableFuture is a combination of Future and Runnable interfaces, that is, this interface representation can be executed asynchronously by threads, because the Runnable interface is implemented, and the execution results of asynchronous tasks of threads can be obtained, because the Future interface is implemented. Therefore, it solves the problem that the Runnable asynchronous task does not return results.

Next let's look at FutureTask. FutureTask implements the RunnableFuture interface, so it's the concrete implementation class of Future and Runnable interface. It's an asynchronous thread task that can be cancelled. It provides the basic implementation of Future, that is, after the asynchronous task is executed, we can get the execution result of the asynchronous task, which is the top priority of our next analysis. FutureTask can wrap a Callable and Runnable object. In addition, FutureTask can be executed by threads and submitted to thread pool for execution.

Let's take a look at the api of the FutureTask class. The key methods are outlined in red.

In the figure above, the run method of FutureTask is executed asynchronously by the thread. The get method is the method to obtain the execution result of asynchronous task, and the cancel method is the method to cancel the execution of task. Next, we focus on these three methods.

reflection:

  1. The return type of the run method overridden by FutureTask is still void, indicating that there is no return value, so how does the get method of FutureTask get the return value?
  2. Can FutureTask's cancel method really cancel the execution of thread asynchronous tasks? Under what circumstances can it be cancelled?

Because FutureTask asynchronous task execution results are also related to the Callable interface, let's look at the Callable interface again:

// Callable.java

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     */
    V call() throws Exception;
}

As we all know, both the callable < V > interface and the Runnable interface can be submitted to the thread pool for execution. The only difference is that the callable < V > interface has returned results, and the generic V is the returned result, while the Runnable interface does not.

Thinking: in general, the Runnable interface implementation class can be submitted to the thread pool for execution. Why can the Callable interface implementation class also be submitted to the thread pool for execution? Think about the internal adaptation of the submit method of thread pool to Callable?

4. Futuretask source code analysis

4.1 FutureTask member variable

Let's first look at the member variables of FutureTask. Understanding these member variables is very important for the following source code analysis.

// FutureTask.java

/** Encapsulated Callable object whose call method is used to perform asynchronous tasks */
private Callable<V> callable;
/** Define a member variable outcome in FutureTask to hold the execution results of asynchronous tasks */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread used to execute the callable task */
private volatile Thread runner;
/** Thread waiting node, an implementation of reinber stack */
private volatile WaitNode waiters;
/** Task execution status */
private volatile int state;

// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
// Offset address of the corresponding member variable state
private static final long stateOffset;
// Offset address of the corresponding member variable runner
private static final long runnerOffset;
// The offset address of the corresponding member variable waiters
private static final long waitersOffset;

Here we should focus on the Callable member variable of FutureTask, because the asynchronous task of FutureTask is ultimately delegated to Callable.

reflection:

  1. The member variables runner,waiters and state of FutureTask are all modified by volatile. We can think about why these three member variables need to be modified by volatile and other member variables don't need to be modified? What is the function of volatile keyword?
  2. Now that the member variables runner,waiters and state have been defined, stateOffset,runnerOffset and waiters offset variables correspond to the offset addresses of runner,waiters and state respectively, why bother?

Let's take a look at the initialization process of three variables: stateOffset,runnerOffset and waitersofffset

// FutureTask.java

static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> k = FutureTask.class;
        stateOffset = UNSAFE.objectFieldOffset
            (k.getDeclaredField("state"));
        runnerOffset = UNSAFE.objectFieldOffset
            (k.getDeclaredField("runner"));
        waitersOffset = UNSAFE.objectFieldOffset
            (k.getDeclaredField("waiters"));
    } catch (Exception e) {
        throw new Error(e);
    }
    }

4.2 state change of futuretask

We talked about the member variables of FutureTask earlier. There is a member variable state that represents the status. We should focus on the state variable, which represents the status of task execution.

// FutureTask.java

/** Task execution status */
private volatile int state;
/** Task new status */
private static final int NEW          = 0;
/** The task is being completed, which is a transient state */
private static final int COMPLETING   = 1;
/** Normal end state of task */
private static final int NORMAL       = 2;
/** Abnormal status of task execution */
private static final int EXCEPTIONAL  = 3;
/** Task cancelled status, corresponding to cancel(false) */
private static final int CANCELLED    = 4;
/** Task interrupt state is a transient state */
private static final int INTERRUPTING = 5;
/** Task interrupted status, corresponding to cancel(true) */
private static final int INTERRUPTED  = 6;

You can see that the task state variable state has the above seven states, and 0-6 corresponds to each state respectively. The task state starts with NEW, and then the three methods of FutureTask, set,setException and cancel, are used to set the state change. There are four cases of state change:

  1. New - > COMPLETING - > normal: this state change indicates the normal end of an asynchronous task, where COMPLETING is a transient transient state, and the state change is set by the set method;
  2. New - > completing - > Excel: this status change indicates that an exception is thrown during the execution of an asynchronous task, and the status change is set by the setException method;
  3. New - > cancel: this status change indicates that it is cancelled, that is, cancel(false) is called, and the cancel method is used to set the status change;
  4. New - > interrupting - > interrupted: this state change indicates that it is interrupted, that is, cancel(true) is called, and the cancel method is used to set the state change.

4.3 FutureTask constructor

FutureTask has two constructors. Let's take a look at them:

// FutureTask.java

// First constructor
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

As you can see, this constructor is useful in the example code of "hot pot" we mentioned earlier, that is, the callable member variable assignment is called when the task is executed asynchronously Callable.call Method to perform asynchronous task logic. In addition, the task status state is assigned to NEW, indicating the task NEW status.

Let's look at another constructor of FutureTask:

// FutureTask.java

// Another constructor
public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

This constructor is executing Executors.callable(runnable, result) is to convert runnable object runnable to callable object through adapter RunnableAdapter, and then assign values to callable and state variables respectively.

Note that what we need to remember here is that when FutureTask is newly created, the task state at this time is NEW.

four point four FutureTask.run Method to perform asynchronous tasks

As we mentioned earlier, FutureTask indirectly implements the Runnable interface and overwrites the run method of the Runnable interface. Therefore, the overwritten run method is submitted to the thread for execution. At the same time, the run method is exactly the method for executing asynchronous task logic. Then, how does the run method save the results of asynchronous task execution after execution?

Let's focus on the following run methods:

// FutureTask.java

public void run() {
    // [1] , in order to prevent multithreading from executing asynchronous tasks concurrently, it is necessary to judge whether the thread is full of conditions for executing asynchronous tasks. There are three situations:
    // 1) If the task state state is NEW and the runner is null, it means that no thread has executed asynchronous tasks. At this time, the conditions for executing asynchronous tasks are met,
    // At the same time, CAS method is called to set the value of the current thread for the member variable runner;
    // 2) If the task state is NEW and the runner is not null, and the task state is NEW but the runner is not null, it indicates that a thread is executing an asynchronous task,
    // At this time, if the conditions for executing asynchronous tasks are not met, return directly;
    // 1) If the task status state is not NEW, no matter whether the runner is null or not, it means that there are threads that have executed asynchronous tasks, and there is no need to restart at this time
    // Execute an asynchronous task, which does not meet the conditions of executing asynchronous task;
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        // The call method of the callable implementation class object passed in the constructor encapsulates the logic of asynchronous task execution
        Callable<V> c = callable;
        // If the task is still in the new state, the asynchronous task is called
        if (c != null && state == NEW) {
            // Asynchronous task execution results
            V result;
            // Asynchronous task execution success or start pass flag
            boolean ran;
            try {
                // [2] , execute asynchronous task logic, and assign the execution result to result
                result = c.call();
                // If no exception is thrown during asynchronous task execution, the asynchronous task execution is successful. At this time, set the ran flag to true
                ran = true;
            } catch (Throwable ex) {
                result = null;
                // An exception is thrown during asynchronous task execution. At this time, the ran flag is set to false
                ran = false;
                // [3] Setting exception, which also sets the change of state state
                setException(ex);
            }
            // [3] If the asynchronous task is executed successfully, set the execution result of the asynchronous task and the change of the status at the same time
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        // During the execution of asynchronous tasks, the runner is always non empty to prevent concurrent calls to the run method. Previously, the cas method was called for judgment
        // After the asynchronous task is executed, whether it ends normally or abnormally, set the runner to null
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        // Task status after thread executing asynchronous task
        int s = state;
        // [4] If the cancel(true) method is executed, the condition is met,
        // The handlePossibleCancellationInterrupt method is called to handle the interrupt
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

You can see that the run method of executing asynchronous tasks is mainly divided into the following four steps:

  1. Judge whether the thread meets the conditions for executing asynchronous tasks: in order to prevent multiple threads from executing asynchronous tasks concurrently, it is necessary to judge whether the thread meets the conditions for executing asynchronous tasks;
  2. If the condition is met, the asynchronous task is executed: because the asynchronous task logic is encapsulated in Callable.call Method, call directly at this time Callable.call Method to execute the asynchronous task, and then return the execution result;
  3. Do different processing according to the execution of asynchronous tasks: 1) if the execution of asynchronous tasks ends normally, call set(result); to set the execution result of tasks; 2) if the execution of asynchronous tasks throws an exception, call setException(ex); to set the exception, please refer to section 4.4.1 for detailed analysis;
  4. Aftermath processing after asynchronous task execution: whether asynchronous task execution succeeds or fails, if other threads call FutureTask.cancel(true). At this time, the handlePossibleCancellationInterrupt method needs to be called to handle the interrupt. For detailed analysis, see section 4.4.2.

It's worth noting that when the thread is full and does not meet the conditions for executing asynchronous tasks, whether the runner is null is determined and set by calling the CAS method compareAndSwapObject of UNSAFE. At the same time, compareAndSwapObject is assigned to the runner through the offset address of the member variable runner, runnerOffset. In addition, the member variable runner is decorated as volatile in the case of multithreading, The setting value of a thread's volatile decorated variable can be brushed into main memory immediately, so the value can be seen by other threads.

4.4.1 set and setException methods of futuretask

Now let's see that when the asynchronous task execution ends normally, set(result) will be called; method:

// FutureTask.java

protected void set(V v) {
    // [1] Call the CAS method of UNSAFE to determine whether the current status of the task is NEW. If it is NEW, set the task status to COMPLETING
    // [think] at this time, the task cannot be executed concurrently by multiple threads. Under what circumstances will the task status not be NEW?
    // The answer is that only when the cancel method is called, the task status is not NEW, and nothing needs to be done at this time,
    // So we need to call CAS method to judge whether the task status is NEW
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // [2] Assign task execution result to member variable outcome
        outcome = v;
        // [3] Set the task status to NORMAL, indicating that the task ends normally
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        // [4] Call the task execution completion method, which will wake up the blocked thread, call the done() method, clear the waiting thread list, etc
        finishCompletion();
    }
}

You can see that when the asynchronous task is completed normally and the asynchronous task is not cancel led, the following things will be done: save the task execution result to the member variable outcome of FutureTask, and call the finishCompletion method to wake up the blocked thread (where is the blocked thread? It will be analyzed later). It is worth noting that the corresponding task state change here is new - > completing - > normal.

Let's continue to see that when an exception is thrown during the execution of an asynchronous task, the setException(ex); method will be called.

// FutureTask.java

protected void setException(Throwable t) {
    // [1] Call the CAS method of UNSAFE to determine whether the current status of the task is NEW. If it is NEW, set the task status to COMPLETING
    // [think] at this time, the task cannot be executed concurrently by multiple threads. Under what circumstances will the task status not be NEW?
    // The answer is that only when the cancel method is called, the task status is not NEW, and nothing needs to be done at this time,
    // So we need to call CAS method to judge whether the task status is NEW
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        // [2] Assign exception to member variable outcome
        outcome = t;
        // [3] Set task status to excel
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        // [4] Call the task execution completion method, which will wake up the blocked thread, call the done() method, clear the waiting thread list, etc
        finishCompletion();
    }
}

It can be seen that the code logic of setException(Throwable t) is almost the same as the previous set(V v). The difference is that an exception is thrown during task execution. At this time, the exception is saved to the member variable outcome of FutureTask. In addition, it is worth noting that the corresponding task state change here is NEW - > COMPLETING - > EXCEPTIONAL.

Because no matter whether the asynchronous task ends normally or abnormally, FutureTask's finishCompletion method will be called to wake up the blocked thread, which means we call Future.get Method, if the asynchronous task is not finished, the thread will block.

// FutureTask.java

private void finishCompletion() {
    // assert state > COMPLETING;
    // Take out the waiting thread chain header node and judge whether the header node is null
    // 1) If the thread chain header node is not empty, remove the waiting thread WaitNode node in the order of "last in, first out" (stack)
    // 2) If the thread chain header node is empty, there is no thread call Future.get() method to get the task execution result without removing
    for (WaitNode q; (q = waiters) != null;) {
        // Call the CAS method of UNSAFE to set the member variable waiters to null
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                // Take out the thread of WaitNode node
                Thread t = q.thread;
                // If the fetched thread is not null, the WaitNode node thread will be null and the blocked thread will be awakened
                if (t != null) {
                    q.thread = null;
                    //[important] wake up the blocked thread
                    LockSupport.unpark(t);
                }
                // Continue to get the next WaitNode thread node
                WaitNode next = q.next;
                // If there is no next WaitNode thread node, it means that all waiting threads have been woken up, and the for loop will jump out
                if (next == null)
                    break;
                // Set the next pointer of the removed thread WaitNode node to null, and then it will be garbage collected
                q.next = null; // unlink to help gc
                // Set the next WaitNode thread node as the current thread WaitNode header node
                q = next;
            }
            break;
        }
    }
    // Whether the task executes normally or throws an exception, the done method is called
    done();
    // Because the asynchronous task has been executed and the result has been saved in the output, you can leave the callable object empty at this time
    callable = null;        // to reduce footprint
}

The finishCompletion method is used to wake up and remove the waiting thread node of the thread waiting list no matter whether the asynchronous task ends normally or abnormally. The list implements a Treiber stack, so the order of wake-up (removal) is "last in, first out", that is, the next first in thread is first awakened (removed). For how the thread waits for the linked list to be linked, we will continue to analyze later.

4.4.2 handlepossible cancelationinterrupt method of futuretask

In the run method analyzed in section 4.4, there is a finally block. At this time, if the task status is state > = interrupting, it means that another thread has executed the cancel(true) method. At this time, it is necessary to release the CPU execution time to other threads for execution. Let's see the specific source code:

// FutureTask.java

private void handlePossibleCancellationInterrupt(int s) {
    // It is possible for our interrupter to stall before getting a
    // chance to interrupt us.  Let's spin-wait patiently.
    // When the task status is INTERRUPTING, let the CPU execute and other threads execute
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield(); // wait out pending interrupt

    // assert state == INTERRUPTED;

    // We want to clear any interrupt we may have received from
    // cancel(true).  However, it is permissible to use interrupts
    // as an independent mechanism for a task to communicate with
    // its caller, and there is no way to clear only the
    // cancellation interrupt.
    //
    // Thread.interrupted();
}

Thinking: when the task state is INTERRUPTING, the time segment of CPU execution should be released at this time? Why call the handlePossibleCancellationInterrupt method after the task is executed?

four point five FutureTask.get Method, get the task execution result

We started a thread to perform asynchronous tasks in its' run 'method. At this time, we can call` FutureTask.get `Method to get the results of asynchronous task execution.

// FutureTask.java

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    //[1] If the task status is < = completing, it means that the task is in the process of execution, at this time, it may end normally or encounter an exception
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    //[2] Finally, the task execution result is returned according to the task status. At this time, there are three situations: 1) the task is executed normally; 2) the task is executed abnormally; 3) the task is cancelled
    return report(s);
}

It can be seen that if the task status state is < = completing, it means that the asynchronous task is in the process of execution, then the awaitDone method will be called to block waiting; when the task is finished, then the report method will be called to report the task results, at this time, there are three situations: 1) the task is executed normally; 2) the task is executed abnormally; 3) the task is cancelled.

4.5.1 FutureTask.awaitDone method

FutureTask.awaitDone Method blocks the current thread that gets the result of the asynchronous task execution until the asynchronous task execution completes.

// FutureTask.java

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    // Calculate timeout end time
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // Thread chain header node
    WaitNode q = null;
    // Whether to join the team
    boolean queued = false;
    // Dead cycle
    for (;;) {
        // If the thread obtaining the execution result of the task is interrupted, remove the WaitNode linked list node of the thread and throw InterruptedException
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        // [5] If the task status is > completing, the task execution result is returned. At this time, the task may end normally and an exception may be thrown
        // Or the task is CANCELLED (a status of cancel, INTERRUPTING or INTERRUPTED)
        if (s > COMPLETING) {
            // [q] at this time, set the thread of the current WaitNode node to null. At the end of the task, call finishCompletion to set the thread of the WaitNode node to null,
            // Why call q.thread = null again here?
            // [answer] because if many threads come to get the task execution results, at the moment when the task is finished, the thread that gets the task is either already in the thread waiting list, or
            // It is also an isolated WaitNode node. All WaitNode nodes in the thread waiting list will be removed (awakened) by finishCompletion
            // Wait for WaitNode node for garbage collection, while the isolated thread WaitNode node is not blocked at this time, so it does not need to be awakened. At this time, just set its attribute to
            // null, then it has no reference by whom, so it can be GC.
            if (q != null)
                q.thread = null;
            // [important] return to task execution result
            return s;
        }
        // [4] If the task status is COMPLETING, it means that the task is in the process of execution. At this time, the thread obtaining the task result needs to give up the CPU execution time segment
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        // [1] If the current thread has not entered the WaitNode node of the thread waiting list, create a new WaitNode node and assign the current thread to the thread attribute of the WaitNode node
        else if (q == null)
            q = new WaitNode();
        // [2] If the current thread waiting node has not yet entered the thread waiting queue, it will be added to the head of the thread waiting queue
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        // If there is a timeout setting, the logic of processing the timeout to get the task result
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        // [3] If there is no timeout setting, the current thread will be blocked directly
        else
            LockSupport.park(this);
    }
}

FutureTask.awaitDone The main tasks of this method are summarized as follows:

  1. First, there is a dead cycle in the awaitDone method;
  2. If the current thread obtaining the result is interrupted by another thread, remove the WaitNode linked list node of the thread and throw InterruptedException;
  3. If the task state is > completing, the task execution result will be returned;
  4. If the task status is COMPLETING, the thread obtaining the task result needs to give up the execution time segment of CPU;
  5. If q == null, the current thread has not been set to the WaitNode node. At this time, create a new WaitNode node and set its thread property to the current thread;
  6. If queued==false, it means that the WaitNode node of the current thread has not joined the thread waiting for the linked list, at this time, the head of the linked list is added;
  7. When timed is set to true, the method has timeout function. The logic of timeout is not analyzed in detail here;
  8. When none of the previous six conditions are met, the current thread is blocked.

We can analyze it here until there is only one thread to execute the asynchronous task, and the result of asynchronous task can be acquired by multiple threads. When the asynchronous task has not been executed, the thread that gets the result of the asynchronous task will join the thread to wait for the list, then call it. LockSupport.park(this); method blocks the current thread. Until the asynchronous task execution is completed, the finishCompletion method will be called to wake up and remove each WaitNode node that the thread waits for the list. Here, the thread that wakes up (removes) the WaitNode node starts from the head of the list, which we have analyzed before.

Another special thing to note is that the awaitDone method is a dead cycle. When a thread getting asynchronous tasks comes in, it may enter multiple conditional branches for different business logic or only one conditional branch. Here are two possible situations:

Case 1: When the thread getting the result of asynchronous task comes in, and the asynchronous task is not finished, that is, state=NEW and there is no timeout setting:

  1. The first cycle: at this time, q = null, enter the judgment branch of the above code label [1], that is, create a WaitNode node for the current thread;
  2. The second cycle: at this time, queued = false, enter the judgment branch of code label [2] above, that is, adding the newly created WaitNode node to the thread waiting list;
  3. The third cycle: at this time, enter the judgment branch of code label [3], that is, block the current thread;
  4. The fourth cycle: after the asynchronous task has been executed, enter the judgment branch of code number [5] above, that is, return the execution result of asynchronous task.

Case 2: When the thread obtaining the asynchronous task result comes in, and the asynchronous task has been executed, that is, state > completing and there is no timeout setting, then directly enter the judgment branch of code label [5] above, that is, directly return the asynchronous task execution result, or join the line waiting list.

4.5.2 FutureTask.report method

In the get method, when the asynchronous task is finished, no matter whether the asynchronous task ends normally or abnormally, or is cancel led, the thread that obtains the asynchronous task result will be awakened, so it will continue to execute FutureTask.report Method reports the execution of an asynchronous task, at which point the result may be returned or an exception may be thrown.

// FutureTask.java

private V report(int s) throws ExecutionException {
    // Assign the asynchronous task execution result to x. at this time, the member variable outcome of FutureTask is either saved
    // The result of normal execution of asynchronous task, or the exception thrown during execution of asynchronous task
    Object x = outcome;
    // [1] If the asynchronous task is completed normally, the asynchronous task execution result will be returned
    if (s == NORMAL)
        return (V)x;
    // [2] If the cancel method has been executed by other threads during asynchronous task execution, a cancelationexception exception will be thrown at this time
    if (s >= CANCELLED)
        throw new CancellationException();
    // [3] If an exception is thrown during the execution of an asynchronous task, the exception will be converted to an ExecutionException and thrown again.
    throw new ExecutionException((Throwable)x);
}

four point six FutureTask.cancel Method, cancel task execution

Let's see at the end FutureTask.cancel Method, as soon as we see FutureTask.cancel Method, it must have been naive at the beginning to think that this is a method that can cancel asynchronous task execution. If we think so, we can only say that we guessed half correctly.

// FutureTask.java

public boolean cancel(boolean mayInterruptIfRunning) {
    // [1] Judge the current task state. If state == NEW, assign the current task state as INTERRUPTING or cancel according to the value of mayInterruptIfRunning parameter
    // a) When the task status is not NEW, it indicates that the asynchronous task has been completed, or an exception has been thrown, or it has been cancelled. In this case, false is returned directly.
    // TODO [question] what if state = COMPLETING? Why return false directly at this time, and can't send the interrupt signal to interrupt the asynchronous task thread??
    // Is TODO just because COMPLETING is an instantaneous state???
    // b) Currently, only when the task status is NEW, if the mayInterruptIfRunning is true, the task status is assigned to INTERRUPTING; otherwise, it is assigned to cancel.
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {    // in case call to interrupt throws exception
        // [2] If mayInterruptIfRunning is true, the thread runner executing the asynchronous task is interrupted at this time (remember to assign the thread executing the asynchronous task to the runner member variable when executing the asynchronous task)
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    // Interrupt the thread runner executing asynchronous tasks
                    t.interrupt();
            } finally { // final state
                // The last task status is assigned as INTERRUPTED
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    // [3] Whether the mayInterruptIfRunning is true or false, the finishCompletion method should be called to wake up the blocked thread obtaining the asynchronous task result and remove the thread waiting for the linked list node
    } finally {
        finishCompletion();
    }
    // Return true
    return true;
}

In the above code, when the asynchronous task state is state! = new, it means that the asynchronous task has completed normal execution or has ended abnormally or has been cancel led, and then it returns false directly; when the asynchronous task state is state = NEW, it can be divided into the following two situations according to whether the mayInterruptIfRunning parameter is true:

  1. When mayInterruptIfRunning = false, the task status state is directly assigned as CANCELLED. At this time, the thread executing asynchronous tasks will not be interrupted. It is worth noting that the corresponding task status change here is new - > CANCELLED.
  2. When mayInterruptIfRunning = true, an interrupt signal will be sent to the thread executing the asynchronous task. It is worth noting that the corresponding task state change here is new - > interrupting - > interrupted.

Finally, no matter whether mayInterruptIfRunning is true or false, at this time, the finishCompletion method should be called to wake up the blocked thread obtaining the asynchronous task result and remove the thread waiting list node.

from FutureTask.cancel In the source code, we can get the answer that this method can not really interrupt the thread executing asynchronous tasks, but can only send an interrupt signal to the thread executing asynchronous tasks. If the thread executing the asynchronous task is in the state of sleep, wait or join, an InterruptedException exception will be thrown, and the thread can be interrupted. In addition, if the asynchronous task needs to be executed in the while loop, the asynchronous task thread can be ended by combining the following code, that is, when the thread executing the asynchronous task is interrupted, the thread executing the asynchronous task can be interrupted Thread.currentThread ().isInterru Pted() returns true. It does not meet the while loop condition, so it exits the loop and ends the asynchronous task execution thread, as shown in the following code:

public Integer call() throws Exception {
    while (!Thread.currentThread().isInterrupted()) {
        // Business logic code
        System.out.println("running...");

    }
    return 666;
}

Note: called FutureTask.cancel Method, as long as the returned result is true, if the asynchronous task thread cannot be interrupted, even if the asynchronous task thread has completed normal execution and returned the execution result, call FutureTask.get Method can not get the execution result of asynchronous task, and cancelationexception exception will be thrown. Do you know why?

Because of the call FutureTask.cancel Method, as long as the returned result is true, the task status is CANCELLED or INTERRUPTED, At the same time, the finishCompletion method will inevitably execute, and the finishCompletion method will wake up the thread that obtains the asynchronous task result waiting list, while the thread that obtains the asynchronous task result wakes up and finds the status s > = cancel led, at this time, the cancelationexception exception will be thrown.

5 Summary

Well, the source code analysis of FutureTask in this article is over. Let's summarize the implementation logic of FutureTask:

  1. We implement the Callable interface and define the business logic to be executed in the overridden call method;
  2. Then, the Callable interface implementation object we implemented is passed to FutureTask, and FutureTask is submitted to the thread as an asynchronous task for execution;
  3. The most important thing is that FutureTask maintains a state internally. Any operation (whether the asynchronous task ends normally or is cancelled) is carried out around this state, and the state of the state task is updated at any time;
  4. Only one thread can execute asynchronous tasks. When the asynchronous task is finished, it may end normally, end abnormally or be cancelled.
  5. Multiple threads can obtain the execution result of asynchronous task concurrently. When the asynchronous task is not finished, the thread obtaining the asynchronous task will join the thread waiting list to wait;
  6. When the asynchronous task thread finishes executing, the thread obtaining the execution result of the asynchronous task will wake up. Note that the wake-up sequence is "last in, first out", that is, the blocked thread added later will wake up first.
  7. When we call FutureTask.cancel Method does not really stop the thread executing asynchronous tasks, only signals to interrupt the thread. However, as long as the cancel method returns true, even if the asynchronous task can be executed normally, we will still throw a cancelationexception exception when we call the get method to get the result.

Extension: we mentioned earlier that the runner,waiters and state of FutureTask are decorated with the volatile keyword, which indicates that these three variables are multithreaded shared objects (member variables) and will be operated by multithreads. At this time, the volatile keyword is decorated to make one thread operate the volatile attribute variable value and be visible to other threads in time. At this time, there will be thread safety problems if only volatile keyword is used for member variable of multithreaded operation. At this time, Doug Lea did not introduce any thread lock, but used CAS method of Unsafe instead of lock operation to ensure thread safety.

What can we learn from the analysis of FutureTask source code?

What is the purpose of our analysis of source code? In addition to understanding the internal implementation principle of FutureTask, we also need to learn from the various skills of big guys in writing framework source code. Only in this way can we grow.

After analyzing the FutureTask source code, we can learn from it:

  1. Using LockSupport to realize the blocking / waking mechanism of threads;
  2. Using the CAS method of volatile and UNSAFE to realize the unlocked operation of shared variables;
  3. To write the logic of timeout exception, please refer to the implementation logic of get(long timeout, TimeUnit unit) of FutureTask;
  4. When obtaining the result of a member variable by multithreading, if the thread needs to wait, it will wait for the logical implementation of the linked list;
  5. An asynchronous task can only be implemented by a single thread at a certain time;
  6. The logical implementation of the change processing of task state state state state in FutureTask.
  7. ...

The above listed points are all places we can learn and reference.

If you think it's good, please forward it mercilessly and like it!

[source code note] Github address:

https://github.com/yuanmabiji/Java-SourceCode-Blogs

Official account [source notes], focusing on the analysis of the source code of the Java backend framework.

Tags: Programming Java github Attribute JDK

Posted on Fri, 26 Jun 2020 00:53:48 -0400 by virtual_odin