什么是 Callable? 什么又是 FutureTask

目录

前言

Callable

FutureTask

如何创建线程?        

FutureTask.run()

FutureTask.get()        

等待线程队列 WaitNode waiters

模拟几个执行流程

怎么唤醒被阻塞的线程呢?

总结

首先梳理一下程序执行的流程

思考

为什么使用 UnSafe 来操作 Future 的几个内部属性呢?

为什么使用维护线程信息?怎么维护线程信息?


前言

        我们前面的文章聊了通过 Thread 和 Runnable 创建一个线程,但是这两种创建线程的方式都是无法获取线程的返回结果的。有时候写业务代码的时候,为了加快任务的执行时间,我们会想要把一个大的任务分割成一个没有依赖关系的小的任务,每个小任务都用一个线程去执行,最终把各个小的任务的结果汇总起来,这样可以大大提高程序执行的效率。但是也面临一个问题,那就是如何获取各个线程的返回结果呢?这就是本文要探讨的内容了。本文使用的是 jdk1.8.0_251。

Callable

        Callable 比较简单,它是一个函数型接口,定义了 call 方法,如下所示

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

        函数型接口一般是预定义的作用。我们知道对象可以在函数之间一直传递,将该对象的能力传递到所有需要它的地方。那么函数也有这个需求,某个函数的能力可能其它函数也需要,所以函数型接口应运而生。比如这里定义了 call 方法,一般会针对不同的业务先写好要处理的逻辑,然后传递给某个方法或者属性,当真正需要的时候调用 Callable 实例的 call 方法就好了,如此便实现了函数的传递。

        需要注意的是,函数型接口跟线程没有必然关系,它可以用到你需要的任何地方,只不过这东西经常跟 Future 一起用,可能就会造成了一些误解。就如 Runnable 也是一个函数型接口,也可以随便在你业务里用,而不是一定跟 Thread 绑定起来。

FutureTask

        前面分析了 Callable 跟线程没有必然联系,那么本文要讨论的关于返回结果的线程看来就要跟 FutureTask 有密不可分的联系了。首先看下 FutureTask 的类信息吧。这会儿可以先混个脸熟,觉得长的可以先不看,直接看下面的使用,等会用了再回来看也不迟。

public class FutureTask<V> implements RunnableFuture<V> {
    
     /**
     * 这是定义了线程的状态,线程状态的变化可能有如下几种
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    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;

    //这里保存了 callable 的引用
    private Callable<V> callable;
    
    // 该变量用于保存线程执行的结果
    private Object outcome; 

    //该变量维护线程的引用
    private volatile Thread runner;
    //这是一个链表,维护线程信息
    private volatile WaitNode waiters;

    // 根据线程状态判断是返回执行结果还是抛出异常
    @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);
    }

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

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

    // 获取任务状态的几个方法
    public boolean isCancelled() {
        return state >= CANCELLED;
    }
    public boolean isDone() {
        return state != NEW;
    }

    // Thread.interrupt 的一层封装
    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;
    }

    // 获取任务的执行结果
    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);
    }

    //将任务执行得到的结果赋值给 outcome。
    protected void set(V v) {
        // 先讲任务状态由 NEW -> COMPLETING
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            //最后再将任务状态设置为 NORMAL
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

    // 将异常信息赋值给返回结果 outcome
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

    // 重写 run 方法,当执行 thread.start() 之后会调用到这里
    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 {
                    // 这里会调用 call() 方法并获取到返回结果
                    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);
        }
    }

    // 这个方法是为那些需要多次执行任务的 run 方法的 task 使用的,比如说设置了定时器每隔一段时间就要执行一次
    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;
    }

    // 当前任务处于 INTERRUPTING 状态的时候,让出 CPU 给其它线程使用
    private void handlePossibleCancellationInterrupt(int s) {
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); // wait out pending interrupt
    }

    // 内部类,拼装成链表结构用来维护等待线程的信息
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

    // 唤醒正在等待的线程
    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
    }

    // 阻塞方法,任务执行结束之后会唤醒
    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);
        }
    }

    // 移除超时或者已经被中断的节点
    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;
            }
        }
    }

    // 通过 Unsafe 来维护几个内部属性信息
    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);
        }
    }

}

        

         从上面的信息可以知道,FutureTask 其实就是 Runnable 的一个实例,只不过它重写和添加了很多方法来处理和影响任务的执行。下面看下 FutureTask 到底怎么用吧。

如何创建线程?        

public class FutureTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {

        FutureTask futureTask = new FutureTask(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread() + "开始执行任务~");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread() + "程序执行耗费了 2s");
                return "success";
            }
        });
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(Thread.currentThread() + "获取到执行结果" + futureTask.get());
    }
}

//打印结果
Thread[Thread-0,5,main]开始执行任务~
Thread[Thread-0,5,main]程序执行耗费了 2s
Thread[main,5,main]获取到执行结果success

public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread() + "开始执行任务~");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "程序执行耗费了 2s");
                //int a = 1/0;
            }
        }, "执行成功");

        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(Thread.currentThread() + "获取到执行结果: " + futureTask.get());
    }
}
//执行结果:
Thread[Thread-0,5,main]开始执行任务~
Thread[Thread-0,5,main]程序执行耗费了 2s
Thread[main,5,main]获取到执行结果: 执行成功

public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread() + "开始执行任务~");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "程序执行耗费了 2s");
                int a = 1/0;
            }
        }, "执行成功");

        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(Thread.currentThread() + "获取到执行结果: " + futureTask.get());
    }
}

//执行结果
Thread[Thread-0,5,main]开始执行任务~
Thread[Thread-0,5,main]程序执行耗费了 2s
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.thread.FutureTest.main(FutureTest.java:32)
Caused by: java.lang.ArithmeticException: / by zero
	at com.thread.FutureTest$1.run(FutureTest.java:26)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.lang.Thread.run(Thread.java:748)

        上面的例子很简单,相信看了就知道什么个意思了,我们也应该知道了我们需要关注的重点就在 FutureTask 跟 Callable 结合获取线程执行任务的返回结果了。我们知道当执行 thread.start() 之后,就会调用 FutureTask 的 run 方法,然后获取结果的时候会调用 FutureTask 的 get 方法,下面我们就着重分析下这两个方法。

FutureTask.run()

public void run() {
    // 首先会判断任务的状态,只有 NEW 状态的 task 才可以执行 run 方法
    // 第二步是通过 unsafe 设置 task 的 runner 变量为 Thread.currentThread(),通过该变量可以中断当前线程
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;

    try {
        // 这个就是传递进来的 callable 实例
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                // 调用 callable 的 call 方法,并将执行结果赋值给 result
                result = c.call();
                // 如果顺利执行获取到返回结果,设置成功标志 ran
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            // 如果调用 call 方法的过程中没有出错,通过 set 方法将 result 赋值给 outcome,这个 set 方法上面介绍 FutureTask 类的时候已经列出,可以去那里看
            if (ran)
                set(result);
        }
    } finally {
        // 解除对线程的引用,代表可以被回收了
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            // 如果该任务正在被中断,那么就让当前线程让出 CPU 资源
            handlePossibleCancellationInterrupt(s);
    }
}

        run 方法的主要任务就是执行 task 的 call 方法,然后将任务的返回结果放入 outcome 字段中进行保存,那么此时应该已经知道,获取线程任务的执行结果也就是获取 outcome 字段的值,下面我们来看下 FutureTask.get() 方法。

FutureTask.get()        

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    // 我们上面分析到,当执行完任务的 call 方法之后,回调用 set(result) 方法保存任务的返回结果并将任务状态修改为 NORMAL, 如果 s <= COMPLETING, 说明 call 还没有执行完成,需要阻塞等待
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    // 程序走到这里说明任务的 call 方法已经执行完成
    return report(s);
}

// 这里先看一下用于返回结果的 report 方法
private V report(int s) throws ExecutionException {
    // 拿到了 outcome 的值
    Object x = outcome;
    // 任务正常执行结束会出于 NORMAL 状态,这个时候直接返回结果就好了
    if (s == NORMAL)
        return (V)x;
    // 说明任务已经被中断了,此时再调用 get 方法就会抛出异常
    if (s >= CANCELLED)
        throw new CancellationException();
    // 这个时候说明 s < NORMAL && s > COMPLETING, 说明程序执行过程中出现了异常
    throw new ExecutionException((Throwable)x);
}

// 再看下阻塞方法,这里只分析从 get 方法调用时的处理流程
// get 方法里传入的两个参数分别为 false, 0L。意味着会一直等不会超时退出
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    // 因为 timed = false, 所以此时 deadline = 0;
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // 这是一个临时变量
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            // 如果阻塞过程中调用 get 方法的线程被中断了,那么就停止阻塞,抛出一个异常
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        if (s > COMPLETING) {
            // 如果任务执行完成,就结束阻塞,返回任务的状态,回到了上面的 get 方法走 report 那一步。并且会取消 task 对线程的引用,让其可以被回收
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING)
            //这里说明 task 的 call 方法已经执行完毕,正在给 outcome 赋值,当前线程让出 CPU 时间片,注意此时的当前线程是调用 get 方法的线程,而不是执行任务的线程。并且让出去了之后并不是永远不再执行饿了,等会时间片轮到自己的时候还是会再次执行的。
            Thread.yield();
        else if (q == null)
            // 初始化一个等待节点,代表着有线程在调用 get 方法等待 task 执行返回结果
            q = new WaitNode();
        else if (!queued)
            // 将当前等待节点放入等待链表中
            // 这是采用链表的头插法,相当于执行 q.next = Waiters, Waiters = q;
            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);
    }
}

        上面三个函数的分析,前两个应该都比较清楚,最后一个 awaitDone 可能还是有不明白的地方,这里先讲几个概念,然后再模拟几个执行情况重新梳理一下执行流程相信大家就完全明白了。

等待线程队列 WaitNode waiters

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

         waiters 是 FutureTask 的一个属性,WaitNode 是 FutureTask 的一个内部类,它只有两个属性,一个是 thread 对象,一个是WaitNode 类型的 next 对象。可见 WaitNode 是对 thread 的一层封装,需要将各个 thread 对象串联成一个链表来管理。

         那么 thread 是什么呢?WaitNode 只有一个无参构造方法,然后就把当前线程赋值给了 thread 对象,所以可见,WaitNode 是由谁实例化出来的,那么 thread 保存的就是那个线程的引用。看下 FutureTask 的代码,我们会发现只有 awaitDone 方法中实例化了 WaitNode 实例,而 awaitDone 又是由 get() 方法调用过来的,所以目前我们就知道了,WaitNode 保存的是调用 get 方法的线程信息。

         至此应该就比较清楚了,实例化 FutureTask 对象之后,可能会有很多线程需要获取到该任务的执行结果,当它们都调用 get 方法
但是 FutureTask 又没有执行完毕无法给出返回结果的时候,就需要用一个链表将对应的线程先维护起来。这个链表的头节点就是 waiters。当 FutureTask 的任务执行完之后就遍历该链表,唤醒正在阻塞的线程并返回结果。

        awaitDone 方法里使用了 Thread.interrupted()  方法,我们前面专门有篇文章讲了该方法有什么用(你真的懂 Thread.interrupt 了吗?)。这里就是一个比较好的应用场景,此时 Future 就认为即然你线程都已经中断了,那么我就认为你调用 get 方法之后就又后悔了,你不想要我的结果了,所以我就把你从等待链表里踢出去,顺便给你抛出一个异常告诉你由于你太着急了,我就不阻塞了,但是也没有给你返回结果。当然这也利用了 LockSupport.park(this) 可以使用中断机制来打断阻塞的机制。不然它在那里一直阻塞也执行不到我们抛异常的那一行。

模拟几个执行流程

  1. 如果线程刚进入 awaitDone 方法,线程就被中断了,那么 if (Thread.interrupted()) 这个条件就满足了,就会立刻抛出一个异常,此时并没有进入阻塞状态。
  2. 如果线程刚进入 awaitDone 方法,FutureTask 任务就执行完毕并给 outcome 赋值成功,那么 if (s > COMPLETING)  条件就满足了,此时就会直接返回,也不会进入阻塞状态。
  3. 如果线程进入 awaitDone 方法之后,如果 FutureTask 任务的 call 方法已经执行完毕,此时正在调用 set(result) 给 outcome 赋值,此时 FutureTask 会短暂的处于 COMPLETING 状态,那么此时 if (s == COMPLETING) 条件就会满足,此时线程就让出 CPU 时间片,让其它线程先去执行。等下次轮到自己再抢到 CPU 时间片的时候,这个赋值过程应该就已经结束了,但是如果还没有结束,那么还会让出时间片,直到 FutureTask 处于 NORMAL 状态。
  4. 如果线程进入 awaitDone 方法的时候,FutureTask 还在执行它的 Callable 的 call 方法,那么此时状态为 NEW, 那么程序就会进入 if (q == null) 的逻辑实例化一个 WaitNode 对象出来,因为 for 循环是个死循环,所以紧接着就会进入第 2 次循环,重新看下前 3 个条件是否满足,如果都不满足就会进去 if (!queued) 的逻辑,将该线程放入等待队列里去,但是此时还是没有将其阻塞,而是决定再给它一次机会,进入第 3 次循环,如果前面三个条件还是没有满足,那么就会进入 LockSupport.park(this) 或者 LockSupport.parkNanos(this, nanos),这取决于调用的 get() 还是 get(long timeout, TimeUnit unit) 方法,至此线程就被阻塞了。
  5. 当线程被唤醒的时候(至于怎么唤醒下一步讲),会再次进入到第 4 次循环,然后此时步骤 2 就会满足,就会返回结果。

怎么唤醒被阻塞的线程呢?

        其实前面已经提到过了,我们知道 FutureTask 的 run 方法调用完 Callable.call() 方法之后,会调用 set(result) 方法给 outcome 赋值,我们再看下这个方法吧。

protected void set(V v) {
    // 先讲任务状态由 NEW -> COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        //给 outcome 赋值
        outcome = v;
        //最后再将任务状态设置为 NORMAL
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
        //任务完成,该方法用于善尾
        finishCompletion();
    }
}

private void finishCompletion() {
    // 遍历等待队列,挨个调用 LockSupport.unpark(t) 来唤醒阻塞的线程
    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;
                // 这里断开节点之间的引用,方便 JVM 进行垃圾回收
                q.next = null; 
                q = next;
            }
            break;
        }
    }
    // FutureTask 并没有用这个方法干什么,它不过你要是愿意的话可以继承下 FutureTask 重写下这个方法实现自己的逻辑,算是 FutureTask 留给我们的扩展点吧
    done();
    // 该任务执行完毕,也就没必要保持 callable 的引用了,等垃圾回收的时候也把它回收了
    callable = null;
}

总结

首先梳理一下程序执行的流程

  1. 首先实例化 Callable 和 FutureTask
  2. 通过 FutureTask 和 Thread 创建一个线程出来
  3. 随着 thread.start 的执行,FutureTask 的 run 方法就会被调用,紧接着 Callable 的 call 方法就会被调用
  4. 当别的线程想要通过 FutureTask 获取任务的执行结果的时候,如果任务执行时间比较长,则调用 get 方法的线程会被阻塞,如果有多个线程同时调用了还在执行过程中的 FutureTask 的 get 方法的话,FutureTask 会将调用的线程通过 waiters 链表维护成一个等待链表。
  5. 当 Callable 的 call 方法执行完成之后,FutureTask 通过便利等待链表 waiters,来唤醒各个等待的线程,并将结果返回给它们。

思考

为什么使用 UnSafe 来操作 Future 的几个内部属性呢?

        为了保证原子性操作,通过上面的分析,我们知道了会有多线程并发访问的情况,使用 UnSafe 在修改属性的时候可以避免出现多个线程同时修改同一个字段而出现的问题。

        比如如果多个线程同时进入等待序列,采用头插法的方式插入节点,如果两个线程获取到的是同一个头节点,并且同时将自己的 next 指向刚刚读到的 waiters 节点,然后再把自己的节点当作最新的头节点,那么最后最新的头节点就是两者中稍微靠后执行的那个节点,前面执行的那个节点数据就丢失了。使用 UnSafe 则可以很好的避免这个问题,因为当一个节点操作等待链表的时候,其它的都必须得等着。

为什么使用维护线程信息?怎么维护线程信息?

        首先需要明确的一点是,这里讨论的线程信息是调用 get 方法的线程信息,这个讨论才是有价值的,如果是执行 FutureTask 的线程,那么肯定就是一个呀,它已经用一个内部变量 runner 维护了。

        FutureTask 需要维护各个调用线程的信息,不然的话当它们阻塞之后就无法将它们唤醒了。那么怎么维护呢?其实方法有很多,比如可以用数组,Map,队列等。这也启示我们,可以根据自己的业务需求定制 FutureTask。

        比如你可以继承一下 FutureTask,主要的逻辑都用 FutureTask 的逻辑,但是维护线程这一块逻辑你给它改了,比如使用一个优先队列,按照线程的名字,或者已经存活的时间来进行排序,可以自定义排序规则,这样的话当任务执行结束的时候就可以优先通知某些线程,当然也可以配置黑名单,比如某些线程的名字我就是看着不顺眼,就是不给你返回结果。

        最后再来讨论一下为什么 FutureTask 默认使用链表来维护外部线程信息呢?我觉得原因之一应该是为了避免大对象,通过我们上面的分析,FutureTask 是很注重内存管理的,比如一旦线程不用了就赶紧解除它的引用,一旦 Callable 不用了也赶紧解除它的引用。这样一方面是为了防止内存泄漏,另一方面也是想赶紧把这些不再使用的对象在 minor gc 的时候就给他清理了。

        但是 JVM 有一个机制就是如果对象占用内存比较大,那么为了避免对象反复的在年轻代 eden 和 survivor 区中反复横跳,就会直接把它放到老年代去,而老年代只有等到 old gc 的时候才会将其回收。而如果使用数组类型的结构来保存线程信息,因为它占用的是连续的内存空间,那么高并发的情况下可能会导致大对象的出现。但是如果使用链表来维护的话,因为各个节点占用分开的内存,就不会出现大对象了。

        此外,因为数组类型的数据结构最大的优势是随机读写,FutureTask 不会用到随机读写的场景,插入节点采用头插法时间复杂度为 O(1), 唤醒线程的时候也是遍历所有的节点,时间复杂度为 O(n), 跟遍历数组的时间复杂度是一样的。再加上链表能避免大对象的产生,所以我理解这应该就是使用链表的原因之一。

        

                

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CallableFutureJava 多线程编程中的两个重要概念。 Callable 接口定义了一个 call() 方法,可以用来返回一个结果并可能抛出异常。与 Runnable 接口不同,Callable 接口的任务执行后可以返回一个值,该值可以通过 Future 接口来获取。 Future 接口用于表示异步计算的结果。它提供了一些方法,可以用来检查计算是否完成、等待计算完成并获取结果等。通过 Future 接口,我们可以在一个线程中启动一个计算任务,并在另一个线程中等待计算的结果。 举个例子,我们可以通过创建一个 Callable 对象来实现一个计算任务,然后将该对象提交给一个 ExecutorService,线程池会在后台启动一个线程来执行该任务。ExecutorService.submit() 方法会返回一个 Future 对象,我们可以通过该对象来获取计算结果。 下面是一个简单的例子: ```java import java.util.concurrent.*; public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> task = () -> { TimeUnit.SECONDS.sleep(1); return 123; }; ExecutorService executor = Executors.newFixedThreadPool(1); Future<Integer> future = executor.submit(task); System.out.println("future done? " + future.isDone()); Integer result = future.get(); System.out.println("future done? " + future.isDone()); System.out.print("result: " + result); executor.shutdown(); } } ``` 输出结果: ``` future done? false future done? true result: 123 ``` 在这个例子中,我们创建了一个 Callable 对象 task,它会在后台睡眠一秒钟后返回 123。然后我们将该对象提交给一个 ExecutorService,线程池会在后台启动一个线程来执行该任务。ExecutorService.submit() 方法会返回一个 Future 对象 future,我们可以通过该对象来获取计算结果。 在主线程中,我们首先检查 future 是否已经完成,然后调用 future.get() 方法来等待计算的结果。由于计算任务需要睡眠一秒钟,因此主线程会在这里等待一秒钟。当计算完成后,future.get() 方法会返回计算结果。最后我们再次检查 future 是否已经完成,并输出计算结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值