FutureTask原理 JDK8

8 篇文章 0 订阅

FutureTask类是并发编程的一个代表异步计算任务类,提供异步计算任务的封装以及异步计算结果的返回。本身实现Runnable接口,可以直接提交给线程池执行。

不过更多应该是配合Callable使用,因为要用到Callable.call()返回的结果封装在自己的outcome变量。

那为什么有了Callable还要用这个类?因为它提供了获取异步计算结果的机制,如果任务没执行完会阻塞获取线程等待任务结束。省去了工程师自己管理的流程。

本文基于JDK 8源码分析。FutureTask已经不依赖AQS的同步状态和同步队列了,直接使用unsafe实现get() run() cancle()等一套

获取结果、等待机制、尝试取消任务这些功能。

首先认识下7种状态。最终状态有:正常结束、异常结束、已取消、已中断

过渡状态有:执行中、中断中

state是维护的核心,外界线程想了解任务的状态就要靠它来完成。

FutureTask.run()原理

JUC下很多无锁并发编程都是基于CAS提供的原子性保证并发安全。Redis分布式锁不正就是利用CAS思想做的。

public void run() {

    // 一开始state=NEW,线程需要把自己写进runner变量。相当于拿锁

    if (state != NEW ||

        !UNSAFE.compareAndSwapObject(this, runnerOffset,

                                     null, Thread.currentThread()))

        return;

    try {

        Callable<V> c = callable;

        // 只有第一个进来的线程会遇到state=NEW,去执行Callable

        if (c != null && state == NEW) {

            V result;

            boolean ran;

            try {

                result = c.call();

                ran = true;

            } catch (Throwable ex) {

                result = null;

                ran = false;

                // 1.2 异常结束,state改为异常

                setException(ex);

            }

            // 1.1 正常运行完毕,设置outcome结果,并唤醒此FutureTask上挂起的线程,拿结果

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

    }

}

1.1 正常结束

protected void set(V v) {

    // 把state从NEW改为COMPLETING,过渡状态

    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {

        // 写结果

        outcome = v;

        // 从COMPLETING改为NORMAL最终状态,代表任务正常结束

        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state

        // 1.1.1 善后工作,唤醒线程

        finishCompletion();

    }

}

1.1.1 善后工作

FutureTask自己建立了一套等待队列机制,简单的Waiter单向链表,所有get()挂起线程都在这里等待。

private void finishCompletion() {

    // assert state > COMPLETING;

    for (WaitNode q; (q = waiters) != null;) {

        // 把waiter链表头结点改为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

}

1.2 异常结束的流程跟正常结束流程基本一致,不同在于将异常对象写进outcome作为结果返回给外界。将state改为exceptional状态。同样的需要唤醒等待队列的线程。

FutureTask.get()原理更简单,直接通过state判断,到达最终状态直接返回outcome,否则按阻塞挂起/超时挂起进入等待队列,

等待其它线程唤醒,再拿结果。

public V get() throws InterruptedException, ExecutionException {

    int s = state;

    // state还处于NEW、COMPLETING,要等待

    if (s <= COMPLETING)

        // 2.1

        s = awaitDone(false, 0L);

    // 2.2 正常结束就返回outcome,异常或被取消就抛出异常。

    return report(s);

}

2.1 看代码相信很快能明白,基本就做了入队并挂起这件事。

值得注意的是线程被封装成waiter是用头插法入队。

超时等待是用指定时间挂起,等待指定时间唤醒后如果state还是未完成,会抛出TimeoutException

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

    }

}

2.2 根据state返回结果/抛出异常

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

}

FutureTask.cancel()原理

cancel()有两种,cancel(true)是中断操作,cancel(false)是取消操作。

区别在于中断是会打断正在执行任务的线程,取消只是将state改为cancelled,不会打断线程。

然后两者都会将等待线程唤醒,线程被唤醒后都会抛出CancelledException

这里需要注意:cancel(false)只是将state改写了,从源码来看,工作线程执行完run()后因为state被改写,不会将结果写outcome。

除此外对runner线程貌似没什么影响。

cancel(true)也只是多了个设置runner线程中断标志位的操作,runner是否真的中断,要看代码怎么写,是否在执行过程中检查标志位,

自行退出。

Thread.interrupt()除此外只会对处于阻塞操作的线程造成直接影响,例如线程阻塞在wait() sleep() join() 等函数中/中断标志被开启了,再进入wait() join() sleep()也会抛出InterruptedException,线程的interrupt标志位会被清除复原,并且立即抛出InterruptedException

public boolean cancel(boolean mayInterruptIfRunning) {

    // 第一步,state=NEW并且将state成功改写为 interrupting/cancelled 就继续向下执行。相当于拿锁

    if (!(state == NEW &&

          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,

              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))

        return false;

    try {    // in case call to interrupt throws exception

        // 是interrupt操作,要尝试打断执行任务的线程

        if (mayInterruptIfRunning) {

            try {

                Thread t = runner;

                if (t != null)

                    t.interrupt();

            } finally { // final state

                // interrupting过渡到最终 interrupted状态

                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);

            }

        }

    } finally {

        // 善后工作,唤醒等待线程

        finishCompletion();

    }

    return true;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值