The use and principle analysis of FutureTask

In the introduction to Java multithreaded programming and the creation of threads, we introduced the ways to create threads, respectively inheriting Thread, implementing Runnable interface and implementing Callable interface. In this article, we introduce another way to create threads, FutureTask. Compared with the implementation of Runnable, there can be no return result. FutureTask is adapted to it to return a default result and supports the creation of Callable threads. Here we first use an example to introduce the use of FutureTask:

private static void futureTaskRunnable() throws InterruptedException, ExecutionException {
    //构造方法为一个接口Runnable实现和一个默认的返回值
    FutureTask<String> future = new FutureTask<String>(()->{
    }, "Hello,Word");
    //调用run()方法运行线程
    future.run();
    System.out.println("futureTaskRunnable: " + future.get());
}
	
private static void futureTaskcallable() throws InterruptedException, ExecutionException {
    //构造方法为接口 Callable的实现
    FutureTask<String> future = new FutureTask<String>(()->{
        return "test";
    });
    //调用run()方法运行线程
    future.run();
    System.out.println("futureTaskcallable: " + future.get());
}

As in the above code, we use Runnable and Callable methods to create a FutureTask, and we can call the run() method of FutureTask to start the thread. According to the class hierarchy diagram of FutureTask, FutureTask not only implements the Future interface, but also implements the Runnable interface. Therefore, FutureTask can be handed over to Executor for execution.

 Earlier we said that FutureTask adapts Runnable so that it can return a default value. Let’s see how FutureTask adapts to Runnable interface. In fact, this is an adapter mode. The following is the implementation of FutureTask adapting Runnable interface.

 public FutureTask(Runnable runnable, V result) {
    //将Runnable接口封装成Callable接口,方法如下
    this.callable = Executors.callable(runnable, result);
    this.state = NEW; 
 }

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
       throw new NullPointerException();
    //适配器模式,下面我们看RunnableAdapter
    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;
    }
}

After introducing the use of FutureTask, we then introduce the implementation principle of FutureTask. In JDK 1.6, Future uses AbstractQueuedSynchronizer. Many concurrency-related tool classes in the JUC package use the AQS class. It can be said that it is the cornerstone of concurrency tools. If you don't know about this class, you can refer to the simple use of AQS queue synchronizer AbstractQueuedSynchronizer. AQS is a synchronization framework that provides a general mechanism to atomically manage the synchronization state, block and wake up threads, and maintain the queue of blocked threads. Every synchronizer based on AQS will contain two types of operations,

  • At least one acquire operation. This operation blocks the calling thread, unless the state of AQS allows this thread to continue execution. The acquire operation of FutureTask is called get()/get(long timeout, TimeUnit unit) method.
  • At least one release operation. This operation changes the state of AQS, and the changed state allows one or more blocked threads to be unblocked. The release operation of FutureTask includes run() method and cancel(...) method

 The specific implementation is not introduced here. The use of AQS is basically similar. In this blog, we mainly introduce the implementation of Future in Task, which is equivalent to the complexity of using AQS. The implementation of FutureTask in JDK1.8 is simpler and clearer. We start from the get() method, the source code is as follows:

public V get() throws InterruptedException, ExecutionException {
    //获取当前FutureTask的状态
    int s = state;
    //如果未完成,加入到等待队列
    if (s <= COMPLETING)
       //加入到等待队列,等待线程执行完毕
       s = awaitDone(false, 0L);
    return report(s);
}

In the get() method, if the task has not been executed, it will be added to the waiting queue to wait for the task to be executed. Here we see how the waiting queue in FutureTask is different from that in AQS. Compared with the waiting queue in AQS, in FutureTask More simple, its definition is as follows:

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

Let's see how FutureTask adds the current thread to the waiting queue and waits for another thread to return before continuing. Its main logic is in the awaitDone() method. Get() and get(long timeout, TimeUnit unit) are finally called The source code of this method is as follows:

//参数time 是否超时,nanos为毫秒数
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    //如果设置了超时,时间相加,否则为0
    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) // 任务正在执行,当前线程变为Ready状态,
            Thread.yield();
        else if (q == null)
            q = new WaitNode(); //创建一个WaitINGNode
        else if (!queued)
            CAS设置值
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        //有超时时间的处理
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
            }
            //调用LockSupport
            LockSupport.parkNanos(this, nanos);
        }
        else
            //调用LockSupport
            LockSupport.park(this);
    }
}

The above code is the process of adding the thread to the waiting queue. The thread will return to continue executing the current thread after the task execution is completed. Below we introduce the logic after the task is executed or cancelled, mainly in the run() method and the cancel method In, it finally calls the finishCompletion() method, the code is as follows:

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;
                //调用unPart唤醒线程
                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
}

 

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108724479