头图

Summary

  1. What is Future
  2. Why do we need Future
  3. Future pattern in Java
  4. Detailed FutureTask

1. What is Future

Future is a common design pattern in multithreaded development. The Future mode can return the contract of the execution result of the thread. Through this contract, the program can choose to retrieve the execution result at the appropriate time. If the thread has not been executed when the result is retrieved, it will block the calling thread and wait for the execution result to return.

2. Why you need Future

In some scenarios, we want to use another thread to perform complex and time-consuming operations. At this time, we don’t want the main thread to wait for waste of CPU. At this time, we can let the main thread do other things first, and then go at the right time. The result of thread execution is retrieved through the Future contract.

3. The Future Pattern in Java

WX20210511-230057@2x.png

The Future pattern in Java is mainly composed of the above interfaces and classes.

3.1 Callable & Runnable

This is our ordinary thread task, in which Callable has a return value (real data), and Runnable does not have a return value. Therefore, when we use Runnable and Future, we must pass in a Result object, which is when we get the result through Future The core code of the Result obtained is as follows:

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

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
        throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}

static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

Callable can only be used with thread pool or Future at present. It cannot be used directly with new Thread(). Runnable can be used with thread pool and new Thread(). When used with Future, it is essentially adapted to it. It is the RunnableAdapter in the above code.

3.2 Future

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

Future is a thread contract. Through its get() method, we can get the result of thread execution. Of course, Future also provides three other methods, namely:

  • cancel: cancel the task
  • isCancelled: Whether the task has been cancelled
  • isDone: Whether the task is completed

3.3 RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {

    void run();
}

The RunnableFuture interface inherits from Runnable and Future, indicating that RunnableFuture can be executed by a thread and the execution result of the thread can be obtained through the contract.

4. FutureTask

4.1 Properties

// 执行任务
private Callable<V> callable;
// 任务的实际执行结果
private Object outcome; 
// 执行任务的线程
private volatile Thread runner;
// 等待结果的线程栈
private volatile WaitNode waiters;

4.2 Status

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;

In addition to the properties in 4.1 of FutureTask, another important property is state. There are about 7 states in FutureTask:

  • NEW: The initial state of the task
  • COMPLETING: Setting the task result
  • NORMAL: The task is completed
  • EXCEPTIONAL: Abnormal task issuance
  • CANCELLED: The task was cancelled
  • INTERRUPTING: Interrupting the task
  • INTERRUPTED: The task was interrupted

4.3 run() method

When the task is executed, the run method is actually executed. The source code is as follows:

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

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

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.
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield(); // wait out pending interrupt
}

The general flow of the run method is as follows:

  1. Check whether the status of the task is NEW and whether there is currently no execution thread, if the check passes, then get the task execution
  2. Invoke the call method of the task
  3. If the execution is abnormal, set the result, modify the status to EXCEPTIONAL, and set the task result to abnormal
  4. If it is executed normally, call set (V v) to set the result, change the state to NORMAL, set the result to the execution result, and wake up the thread waiting for the result
  5. Finally, in the finally block, we set the runner attribute to null and check whether there are any missing interrupts. If we find that s >= INTERRUPTING, it means that the thread executing the task may be interrupted, because s >= INTERRUPTING has only two possibilities. The state state is INTERRUPTING and INTERRUPTED.

4.3 get() method

When we need to get the result of FutureTask, we need to call the get method to get the result.


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

@SuppressWarnings("unchecked")
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);
}

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

The general steps to obtain the results are as follows:

  1. Check whether the task status is NEW or COMPLETING. If it is not, it means it has been executed successfully or failed, and the result is returned.
  2. Otherwise, block waiting, the steps of blocking waiting are as follows
  3. Check whether the current thread is interrupted, if it is, remove it from the waiting thread
  4. Check the task status again, if it is abnormal, interrupted, or execution completed status, the result will be returned directly.
  5. If the task is in the COMPLETING state, it indicates that the task has been executed and the result is being set. At this time, let the thread that obtains the result give up the CPU to wait for
  6. If the thread stack waiting for the result is null, indicating that it has not been generated yet, then the thread stack waiting for the result is generated
  7. If queued is false, it means that the thread waiting for the result has not yet been put on the stack, so put it on the stack
  8. Finally, see whether it is a timeout waiting, according to whether it is timeout, choose whether the thread waiting for the result will be permanently suspended (waiting for wake-up) or a suspension with a timeout period

This is the end of this issue of Java Future. I’m shysh95. See you in the next issue!


shysh
82 声望17 粉丝

« 上一篇
锁优化