Abstract: This article analyzes two asynchronous models in the form of actual cases, and deeply analyzes the Future interface and FutureTask class from the perspective of source code.

This article is shared from the Huawei Cloud Community " [Proficient in High Concurrency Series] Two asynchronous models and in-depth analysis of the Future interface (1)! ", author: Binghe.

This article analyzes the two asynchronous models in the form of actual cases, and deeply analyzes the Future interface and FutureTask class from the source point of view. I hope you can take your heart, open your IDE, and follow the article to read the source code. I believe you must have gained a lot!

One or two asynchronous models

In Java's concurrent programming, there are roughly two types of asynchronous programming models. One is to directly run other tasks in parallel in an asynchronous manner without returning the result data of the task. One type is to run other tasks asynchronously and need to return results.

1. Asynchronous model with no results

Asynchronous tasks that do not return results can be directly thrown into the thread or thread pool to run. At this time, the execution result data of the task cannot be directly obtained. One way is to use the callback method to obtain the running result of the task.

The specific plan is: define a callback interface, and define the method of receiving task result data in the interface, and the specific logic is completed in the implementation class of the callback interface. Put the callback interface and task parameters into the thread or thread pool to run. After the task runs, call the interface method and execute the logic in the callback interface implementation class to process the result data. Here, a simple example is given for reference.

  • Define the callback interface
package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 定义回调接口
 */
public interface TaskCallable<T> {
    T callable(T t);
}

To facilitate the universal type of the interface, here is the definition of the generic type for the callback interface.

  • Define the encapsulation class of task result data
package io.binghe.concurrent.lab04;

import java.io.Serializable;

/**
 * @author binghe
 * @version 1.0.0
 * @description 任务执行结果
 */
public class TaskResult implements Serializable {
    private static final long serialVersionUID = 8678277072402730062L;
    /**
     * 任务状态
     */
    private Integer taskStatus;

    /**
     * 任务消息
     */
    private String taskMessage;

    /**
     * 任务结果数据
     */
    private String taskResult;
 
    //省略getter和setter方法
    @Override
    public String toString() {
        return "TaskResult{" +
                "taskStatus=" + taskStatus +
                ", taskMessage='" + taskMessage + '\'' +
                ", taskResult='" + taskResult + '\'' +
                '}';
    }
}
  • Create the implementation class of the callback interface

The implementation class of the callback interface is mainly used to perform corresponding business processing on the return result of the task. Here, for the convenience of demonstration, only the result data is returned. You need to do corresponding analysis and processing according to specific business scenarios.

package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 回调函数的实现类
 */
public class TaskHandler implements TaskCallable<TaskResult> {
    @Override
public TaskResult callable(TaskResult taskResult) {
//TODO 拿到结果数据后进一步处理
    System.out.println(taskResult.toString());
        return taskResult;
    }
}
  • Create task execution class

The task execution class is the class that performs the task specifically, which implements the Runnable interface. In this class, define a member variable of the callback interface type and a task parameter of the String type (parameters of the simulation task), and inject the callback interface and Task parameters. The task is executed in the run method. After the task is completed, the result data of the task is encapsulated into a TaskResult object, and the method of the callback interface is called to pass the TaskResult object to the callback method.

package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 任务执行类
 */
public class TaskExecutor implements Runnable{
    private TaskCallable<TaskResult> taskCallable;
    private String taskParameter;

    public TaskExecutor(TaskCallable<TaskResult> taskCallable, String taskParameter){
        this.taskCallable = taskCallable;
        this.taskParameter = taskParameter;
    }

    @Override
    public void run() {
        //TODO 一系列业务逻辑,将结果数据封装成TaskResult对象并返回
        TaskResult result = new TaskResult();
        result.setTaskStatus(1);
        result.setTaskMessage(this.taskParameter);
        result.setTaskResult("异步回调成功");
        taskCallable.callable(result);
    }
}

At this point, the entire framework is complete. The next step is to test to see if the results of asynchronous tasks can be obtained.

  • Asynchronous task test class
package io.binghe.concurrent.lab04;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试回调
 */
public class TaskCallableTest {
    public static void main(String[] args){
        TaskCallable<TaskResult> taskCallable = new TaskHandler();
        TaskExecutor taskExecutor = new TaskExecutor(taskCallable, "测试回调任务");
        new Thread(taskExecutor).start();
    }
}

In the test class, use the Thread class to create a new thread and start the thread to run tasks. The final interface data of the running program is shown below.

TaskResult{taskStatus=1, taskMessage='测试回调任务', taskResult='异步回调成功'}

You can savour this way of obtaining asynchronous results. Here, it simply uses the Thread class to create and start threads, or it can be implemented in a thread pool. You can implement the thread pool to obtain asynchronous results through the callback interface.

2. Asynchronous model that returns results

Although the results of asynchronous tasks can be obtained by using the callback interface, this method is slightly more complicated to use. The JDK provides a processing solution that can directly return asynchronous results. The most commonly used is to use the Future interface or its implementation class FutureTask to receive the return result of the task.

  • Use the Future interface to get asynchronous results

The Future interface is often used in conjunction with the thread pool to obtain asynchronous execution results, as shown below.

package io.binghe.concurrent.lab04;

import java.util.concurrent.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试Future获取异步结果
 */
public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "测试Future获取异步结果";
            }
        });
        System.out.println(future.get());
        executorService.shutdown();
    }
}

The result of the operation is shown below.

Test Future to obtain asynchronous results

  • Use FutureTask class to get asynchronous results

The FutureTask class can be used in combination with the Thread class or the thread pool. Next, let's look at these two ways of using it.

An example of use combined with the Thread class is shown below.

package io.binghe.concurrent.lab04;

import java.util.concurrent.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试FutureTask获取异步结果
 */
public class FutureTaskTest {

    public static void main(String[] args)throws ExecutionException, InterruptedException{
        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "测试FutureTask获取异步结果";
            }
        });
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}

The result of the operation is shown below.

Test FutureTask to obtain asynchronous results
An example of the use of the combined thread pool is as follows.

package io.binghe.concurrent.lab04;

import java.util.concurrent.*;

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试FutureTask获取异步结果
 */
public class FutureTaskTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "测试FutureTask获取异步结果";
            }
        });
        executorService.execute(futureTask);
        System.out.println(futureTask.get());
        executorService.shutdown();
    }
}

The result of the operation is shown below.

test FutureTask to obtain asynchronous results
You can see that using the Future interface or FutureTask class to obtain asynchronous results is much simpler than using the callback interface to obtain asynchronous results. Note: There are many ways to achieve asynchronous, here is just an example of multi-threading.

Second, in-depth analysis of the Future interface

1.Future interface

Future is a new asynchronous programming interface added to JDK1.5. Its source code is shown below.

package java.util.concurrent;

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

As you can see, a total of 5 abstract methods are defined in the Future interface. Next, I will introduce the meaning of these 5 methods respectively.

cancel(boolean)
To cancel the execution of the task, receive a boolean type parameter, and return true if the task is successfully canceled, otherwise it returns false. When the task has been completed, has ended, or cannot be cancelled due to other reasons, the method will return false, indicating that the task has failed to be cancelled. When this method is called when the task is not started, and the result returns true (cancelled successfully), the current task will no longer run. If the task has been started, it will decide whether to interrupt the currently running thread to cancel the currently running task according to the boolean type parameter currently passed.

  • isCancelled()

Judge whether the task is canceled before completion, if it is canceled before the task is completed, return true; otherwise, return false.

One detail needs to be paid attention to here: only if the task is not started or cancelled before completion, will it return true, indicating that the task has been successfully cancelled. Otherwise, it will return false.

  • isDone()

Judge whether the task has been completed. If the task ends normally, exits with an exception, or is canceled, it will return true, indicating that the task has been completed.

There is one detail that needs to be paid attention to here: only the task is not started or is cancelled before completion, it will return true, indicating that the task has been successfully cancelled. Otherwise, it will return false.

  • isDone()

Judge whether the task has been completed. If the task ends normally, exits with an exception, or is canceled, it will return true, indicating that the task has been completed.

  • get()

When the task is completed, the result data of the task is directly returned; when the task is not completed, the task is completed and the result data of the task is returned.

  • get(long, TimeUnit)

When the task is completed, the result data of the task is directly returned; when the task is not completed, it waits for the task to complete, and the timeout waiting time is set. If the task is completed within the timeout period, the result will be returned; otherwise, a TimeoutException will be thrown.

2.RunnableFuture interface

The Future interface has an important sub-interface, that is, the RunnableFuture interface. The RunnableFuture interface not only inherits the Future interface, but also inherits the java.lang.Runnable interface. The source code is as follows.

package java.util.concurrent;

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

Here, ask, how many abstract methods are there in the RunnableFuture interface? Let's talk about it when you think about it! Hahaha. . .

This interface is relatively simple. The run() method is the method called when running a task.

3.FutureTask class

The FutureTask class is a very important implementation class of the RunnableFuture interface. It implements all methods of the RunnableFuture interface, the Future interface and the Runnable interface. There are a lot of source codes for FutureTask, so I won’t paste this one, and you can check it under java.util.concurrent.

(1) Variables and constants in the FutureTask class

In the FutureTask class, a state variable state is first defined. This variable is modified with the volatile keyword. Here, everyone only needs to know that the volatile keyword achieves thread safety through memory barriers and prohibition of reordering optimization. Later, we will analyze the volatile key separately. How does the word guarantee thread safety. Then, the state constants of several tasks are defined when they are running, as shown below.

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;

Among them, several possible state change processes are given in the code comments, as shown below.

NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED

Next, several other member variables are defined, as shown below.

private Callable<V> callable;
private Object outcome; 
private volatile Thread runner;
private volatile WaitNode waiters;

I have seen the Callable interface that we are familiar with again. The Callable interface must be used to call the call() method to perform specific tasks.

  • outcome: Object type, representing the outcome data or abnormal information obtained through the get() method.
  • Runner: The thread that runs the Callable will use CAS to ensure thread safety during its operation. Here, everyone only needs to know that CAS is a way for Java to ensure thread safety. A follow-up article will deeply analyze how CAS guarantees thread safety.
  • waiters: WaitNode type variable, representing the stack of waiting threads. In the implementation of FutureTask, the running state of the task will be exchanged through CAS combined with this stack.

Take a look at the definition of the WaitNode class as shown below.

static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() { thread = Thread.currentThread(); }
}

As you can see, the WaitNode class is a static internal class of the FutureTask class. A Thread member variable and a reference to the next WaitNode node are defined in the class. The thread variable is set to the current thread through the construction method.

(2) Construction method

Next, there are two construction methods of FutureTask, which are relatively simple, as shown below.

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;
}

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;
}

(3) Whether to cancel and how to complete

Continue to look down at the source code, and see the method of whether a task is cancelled, and the method of whether a task is completed, as shown below.

public boolean isCancelled() {
    return state >= CANCELLED;
}

public boolean isDone() {
    return state != NEW;
}

In these two methods, whether the task has been cancelled and completed is determined by judging the status of the task. Why would you judge this way? Looking again at the state constants defined in the FutureTask class, it is found that the definition of its constants is regular and not arbitrarily defined. Among them, the constants greater than or equal to CANCELLED are CANCELLED, INTERRUPTING and INTERRUPTED, these three states can indicate that the thread has been cancelled. When the status is not equal to NEW, it can indicate that the task has been completed.

Through this, you can learn one thing: in the future, in the coding process, you must define the state you use according to the law, especially when it involves frequent state changes in the business. The regular state can make business processing become You can get twice the result with half the effort. This is also learned by looking at other people's source code designs. Here, I suggest you read more of the source code of excellent open source frameworks written by others.

(4) How to cancel

We continue to look down at the source code. Next, we see the cancel (boolean) method, as shown below.

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;
}

Next, disassemble the cancel (boolean) method. In the cancel (boolean) method, first determine the status of the task and the result of the CAS operation. If the status of the task is not equal to NEW or the CAS operation returns false, then it directly returns false, indicating that the task cancellation failed. As follows.

if (!(state == NEW &&
      UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
          mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
    return false;

Next, in the try code block, first determine whether the thread of the current task can be interrupted to cancel the task. If the thread where the current task is located can be interrupted, use a Thread temporary variable to point to the thread running the task. When the pointed variable is not empty, call the interrupt() method of the thread object to interrupt the running of the thread, and finally mark the thread as The interrupted state. As follows.

try {
    if (mayInterruptIfRunning) {
        try {
            Thread t = runner;
            if (t != null)
                t.interrupt();
        } finally { // final state
            UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
        }
    }
}

Here, it is found that the UNSAFE.putOrderedInt() method is used to change the state of the task. What the hell is this method? Click in and take a look, as shown below.

public native void putOrderedInt(Object var1, long var2, int var4);

As you can see, it's a local method again. Hey, don't care about it here. Follow-up articles will explain the role of these methods in detail.

Next, the cancel (boolean) method will enter the finally code block, as shown below.

finally {
    finishCompletion();
}

You can see that the finishCompletion() method is called in the finally code block. As the name suggests, the finishCompletion() method means the end of the task. Let’s see how it is implemented. Point to the finishCompletion() method to take a look, as shown below.

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        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
}

In the finishCompletion() method, first define a for loop, the loop termination factor is waiters is null, in the loop, it is judged whether the CAS operation is successful, and if it succeeds, the logic in the if condition is performed. First, define a for spin loop, in the body of the spin loop, wake up the thread in the WaitNode stack to complete its operation. When the threads in the WaitNode stack have finished running, exit the outer for loop through break. Next, call the done() method. What the hell is the done() method? Click in and take a look, as shown below.


protected void done() { }

As you can see, the done() method is an empty method body, which is handed over to subclasses to implement specific business logic.

When we need to perform some additional business logic when canceling tasks in our specific business, we can override the implementation of the done() method in the subclass.

(5) get() method

Continuing to look down at the code of the FutureTask class, two get() methods are implemented in the FutureTask class, as shown below.

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

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);
}

The get() method with no parameters is that when the task is not completed, it will block until the task result is returned. The get() method with parameters is that when the task is not completed and the waiting time exceeds the timeout period, a TimeoutException will occur.

The main logic of the two get() methods is similar, one has no timeout setting, and the other has a timeout setting. Here is the main logic. To determine whether the current state of the task is less than or equal to COMPLETING, that is, the task is NEW or COMPLETING, call the awaitDone() method, and look at the implementation of the awaitDone() method, as shown below.

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 (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)
            q = new WaitNode();
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}

Next, disassemble the awaitDone() method. In the awaitDone() method, the most important thing is the for spin loop. In the loop, first determine whether the current thread is interrupted. If it has been interrupted, call removeWaiter() to remove the current thread from the stack and throw InterruptedException The exception is as shown below.

if (Thread.interrupted()) {
    removeWaiter(q);
    throw new InterruptedException();
}

Next, determine whether the current state of the task is complete. If it is completed and the stack handle is not empty, set the current thread in the stack to empty and return to the current task state, as shown below.

int s = state;
if (s > COMPLETING) {
    if (q != null)
        q.thread = null;
    return s;
}

When the status of the task is COMPLETING, make the current thread give up CPU resources, as shown below.

else if (s == COMPLETING)
    Thread.yield();

If the stack is empty, create a stack object as shown below.

else if (q == null)
    q = new WaitNode();

If the queued variable is false, assign a value to queued by CAS operation. If the timed parameter passed by the awaitDone() method is true, the timeout time is calculated. When the time has expired, the current thread is removed from the stack and the task status is returned, as shown below Show. If it does not time out, reset the timeout period as shown below.

else if (!queued)
    queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
else if (timed) {
    nanos = deadline - System.nanoTime();
    if (nanos <= 0L) {
        removeWaiter(q);
        return state;
    }
    LockSupport.parkNanos(this, nanos);
}

If all the above conditions are not met, the current thread is set to the waiting state, as shown below.

else
    LockSupport.park(this);

Next, go back to the get() method. When the awaitDone() method returns a result, or the status of the task does not meet the conditions, the report() method will be called, and the current task status will be passed to the report() method, and The result is returned as shown below.

return report(s);
It seems that we have to look at the report() method here, click in to see the implementation of the report() method, as shown below.

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);
}

It can be seen that the implementation of the report() method is relatively simple. First, assign the output data to the x variable. Next, the main task is to determine the status of the received task. If the status is NORMAL, the x will be forced to return to the generic type. ; When the status of the task is greater than or equal to CANCELLED, that is, the task has been canceled, a CancellationException is thrown, and in other cases, an ExecutionException is thrown.

At this point, the get() method analysis is complete. Note: Be sure to understand the implementation of the get() method, because the get() method is a frequently used method when we use the Future interface and the FutureTask class.

(6) set() method and setException() method

Continuing to look at the code of the FutureTask class, the next thing to see is the set() method and setException() method, as shown below.

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

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

It can be seen from the source code that the overall logic of the set() method is almost the same as the setException() method, except that one sets the state to NORMAL and the other sets the state to EXCEPTIONAL when setting the task state.

As for the finishCompletion() method, it has been analyzed before.

(7) run() method and runAndReset() method

Next is the run() method. The source code of the run() method is shown below.

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);
    }
}

It can be said that as long as Future and FutureTask are used, the run() method will inevitably be called to run the task. It is very necessary to master the flow of the run() method. In the run() method, if the current state is not NEW, or the result returned by the CAS operation is false, it returns directly without executing subsequent logic, as shown below.

if (state != NEW ||
    !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
    return;

Next, in the try code block, assign the member variable callable to a temporary variable c, determine that the temporary variable is not equal to null, and the task status is NEW, then call the call() method of the Callable interface and receive the result data. And set the ran variable to true. When the program throws an exception, the variable receiving the result is set to null, the ran variable is set to false, and the setException() method is called to set the status of the task to EXCEPTIONA. Next, if the ran variable is true, call the set() method as shown below.

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);
    }
}

Next, the program will enter the finally code block, as shown below.

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);
}

Here, the runner is set to null, if the current state of the task is greater than or equal to INTERRUPTING, that is, the thread is interrupted. Then call the handlePossibleCancellationInterrupt() method. Next, look at the implementation of the handlePossibleCancellationInterrupt() method.

private void handlePossibleCancellationInterrupt(int s) {
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield();
}

It can be seen that the implementation of the handlePossibleCancellationInterrupt() method is relatively simple. When the status of the task is INTERRUPTING, the while() loop is used. The condition is that the current task status is INTERRUPTING, and the CPU resources occupied by the current thread are released, that is, when the task After the operation is completed, the resources occupied by the thread are released.

The logic of the runAndReset() method is similar to that of run(), except that the runAndReset() method will reset the task state to NEW in the finally code block. The source code of the runAndReset() method is shown below, so the explanation will not be repeated.

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 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
        s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
    return ran && s == NEW;
}

(8) removeWaiter() method

The removeWaiter() method mainly uses a spin loop to remove threads in WaitNode, which is relatively simple, as shown below.

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;
        }
    }
}

Finally, at the end of the FutureTask class, there is the following code.

// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset;
private static final long runnerOffset;
private static final long waitersOffset;
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);
    }
}

The role of these codes will be explained in detail in the subsequent in-depth analysis of CAS articles, so I will not discuss them here.

At this point, the analysis of the source code of the Future interface and the FutureTask class is complete.

Click to follow and learn about Huawei Cloud's fresh technology for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量