FutrueTask原理分析

    通常一个请求分为请求-处理-返回,如果通过异步线程去完成一个任务,我们通常会选择FutureTask +Submit+ Callable()来实现获取线程的返回值。 FutureTask继承了Future和Runnable,Future代表了线程的生命周期的状态机,而Runnable则通过这个状态去获取处理的结果。

1、应用实例

问题思考: 如何让一个线程有返回值?
应用代码示例:通过FutureTask + Callable来实现线程的返回值;
public class FutureTaskTest implements Callable<String> {

    @Override
    public String call() throws Exception {
        return "hello callable";
    }


    public static void main(String[] args) throws Exception {

        FutureTaskTest futureTaskTest = new FutureTaskTest();
        FutureTask<String> futureTask = new FutureTask<>(futureTaskTest);
        new Thread(futureTask).start();       //线程一:线程池或者创建线程;
        System.out.println(futureTask.get()); //线程二:阻塞获取结果
        
        //线程池的支持
        ExecutorService executors = new ThreadPoolExecutor(5, 10, 20, TimeUnit.MINUTES,
            new LinkedBlockingQueue<Runnable>(1000));
        //submit不会抛异常,除非调用future.get(); execute()会抛出
        Future<String> future = executors.submit(futureTaskTest);
        System.out.println(future.get());
    }
}

2、Future

Future表示一个任务线程的生命周期,通过线程状态机(完成-异常-正在执行等)来控制线程,提供相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等,方法比较简单。
具体线程功能控制函数如下:
public interface Future<V> {
        // 发送取消命令给线程并返回是否发送成功;
    boolean cancel(boolean mayInterruptIfRunning);

    // 当前的Future是否被取消,返回true表示已取消
    boolean isCancelled();

    // 当前Future是否已结束。包括运行完成、抛出异常以及取消,都表示当前Future已结束;
    boolean isDone();

    // 获取Future的结果值。如果当前Future还没有结束,那么当前线程就等待,直到Future运行结束,那么会唤醒等待结果值的线程的。
    V get() throws InterruptedException, ExecutionException;

    // 获取Future的结果值。与get()相比较多了允许设置超时时间
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

3、FutureTask-实现原理

FutureTask的类图:

3.1、生产消费者模型

 从class类图可以看出,FutureTask是Runnable和Future的结合。我们可以把Runnable比作是生产者,Future比作是消费者,生产者运行任务run()方法计算结果,消费者通过get()方法获取结果。 作为生产者消费者模式,有一个很重要的机制,就是如果生产者数据还没准备的时候,消费者会被阻塞;当生产者数据准备好了以后会唤醒消费者继续执行。 
  • 生产者:Runnable 生产计算结果;
  • 消费者:future- 线程的生命周期,通过 线程的状态机来判断线程是否执行结束;从而异步获取结果;
  • futureTask:将Future和Runnable两者结合起来
这里结果的获取get()涉及到2个线程的协作:
  • 第一个线程:执行Callable()的任务:执行callable业务方法call方法,返回结果值result;
  • 第二个线程:通过 回调函数get()方法来获取 线程的执行状态state来异步获取结果;
    • 如果没有执行完毕:则被- awaitDone() - park()阻塞加入到阻塞链表中等待线程执行;
    • 如果执行完毕: finishCompletion(),唤醒阻塞的线程获取结果;
  • 单链表实现阻塞线程的存储;
  • 生产消费模式通过Supportlock实现线程的阻塞park()和唤醒unpark();

3.2、state的含义

表示FutureTask当前的状态,分为七种状态:
private volatile int state;
// NEW 新建状态,表示这个FutureTask还没有开始运行
private static final int NEW          = 0;
// COMPLETING 完成状态, 表示FutureTask任务已经计算完毕了但是还有一些后续操作,例如唤醒等待线程操作,还没有完成;
private static final int COMPLETING   = 1;
// FutureTask任务完结,正常完成,没有发生异常;
private static final int NORMAL       = 2;
// FutureTask任务完结,因为发生异常;
private static final int EXCEPTIONAL  = 3;
// FutureTask任务完结,因为取消任务;
private static final int CANCELLED    = 4;
// FutureTask任务完结,也是取消任务,不过发起了中断运行任务线程的中断请求;
private static final int INTERRUPTING = 5;
// FutureTask任务完结,也是取消任务,已经完成了中断运行任务线程的中断请求;
private static final int INTERRUPTED  = 6;

4、源码分析

线程一:run()方法调用call方法执行任务task;

4.1、run()方法

    从类图发现FutureTask实现了Runnable(),所以一定有一个run()方法,看看run方法里做了什么?
    其实run()方法作用非常简单,就是调用callable的call方法返回结果值result,根据是否发生异常,调用 set(result)或 setException(ex)方法表示 FutureTask 任务完结。不过因为FutureTask任务都是在多线程环境中使用,所以要注意并发冲突问题。
    注意在run()方法中,我们没有使用Synchronized代码块或者Lock来解决并发问题,而是使用了CAS这个乐观锁来实现并发安全,保证只有一个线程能运行FutureTask任务.
public class FutureTask<V> implements RunnableFuture<V> {

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

public void run() {
    if (state != NEW ||            //线程安全保证:使用cas来保证只有一个线程能运行task任务,而不是使用synchronized或者lock
        !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;  //传进来的任务实例对象 c不为null且状态为新建的状态;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call(); //调用任务实例的call方法,并不是创建一个新的线程;
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex); //设置异常结果
            }
            if (ran)
                set(result);      //设置程序的运行结果
        }
    } finally {
        runner = null;

        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

当线程执行结束以后,唤醒等待结果的Runnable线程,通过set设置返回结果:

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();                 // 执行完成,通知获取结果的线程获取结果
    }
}
当执行任务的线程执行结束,然后唤醒等待结果的get线程获取结果:
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
}

4.2、get()方法

阻塞获取结果的get函数,如果发现执行任务的线程没有结束,则直接进入阻塞等待状态。get()的主要作用:
  • 线程未执行结束的时候,状态小于complete,调用awaitDone方法,其实也是调用 park() 阻塞获取结果的线程,将该线程插入到 单链表等待队列;
  •  run()方法执行完set值的时候通过调用unpark()唤醒获取结果的线程,获取结果或者抛出异常;
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        s = awaitDone(false, 0L); //如果线程还没有执行完,就会被阻塞;
    return report(s);
}
如果当前的结果还没有被执行完,把当前线程线程和插入到等待队列,阻塞取结果线程,直至任务线程执行结束时被唤醒unpark():
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;           //wait节点,构建一条单链表记录等待的线程
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }// 当状态大于COMPLETING时,表示FutureTask任务已结束。
        else if (s == COMPLETING) // cannot time out yet 
            Thread.yield();
        //  将当前阻塞线程插入到等待线程链表中;
        else if (q == null)
            q = new WaitNode();
        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.parkNanos(this, nanos); //超时阻塞
        }
        else
            LockSupport.park(this);             //没有设置超时时间的阻塞;
    }
}
//等待节点结构
static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next; //单向链表
    WaitNode() { thread = Thread.currentThread(); } //保存当前的阻塞的线程
}

4.3、report()

report方法就是根据传入的状态值 s,来决定是抛出异常,还是返回结果值,这个两种情况都 表示 FutureTask 完结了。
private V report(int s) throws ExecutionException {
    Object x = outcome; //表示call的返回值;
    if (s == NORMAL)    //表示正常完结状态;
        return (V)x;
    if (s >= CANCELLED)// 大于或等于CANCELLED,都表示手动取消FutureTask任务,所以抛出CancellationException异常;
        throw new CancellationException();
    // 否则就是运行过程中,发生了异常,这里就抛出异常
    throw new ExecutionException((Throwable)x);
}

到这里,通过futureTask来获取线程的返回值的流程就分析完了,因为线程是异步执行,要想获取它的结果,必须的有另外一个线程来监视它的执行过程,然后才能获取执行结果。

5、submit和execute的区别

回头再来看看线程池执行任务的两种方法:submit 和 execute,两者有什么区别呢?

5.1、execute

  • 1、execute 只能接受一个Runnable任务;
  • 2、execute如果 出现异常会直接抛出
  • 3、execute 没有返回值;
public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

5.2、submit

  • 1、submit可以接受Runnable 和Callable这两种类型的参数;
  • 2、对于submit方法,如果传入一个Callable,可以得到一个Future的返回值; Callable + Future
  • 3、submit方法调用 不会抛异常,除非调用 Future.get ();
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    // 将task封装了一个RunnableFuture
    RunnableFuture<T> ftask = newTaskFor(task); 
    //交由线程池区处理ftask任务;
    execute(ftask);
    return ftask;
}

结论:通过源码可以看出,调用的submit方法,这里其实相对于execute方法来说,只多做了一步操作,就是 封装了一个RunnableFuture,然后调用execute方法,这里就是调用线程池里的worker线程来调用过ftask的run方法,执行任务,而这个ftask其实就是FutureTask里面最终实现的任务逻辑。

6、使用注意事项

  1. 异步阻塞获取结果需要加上超时时间,否则一致阻塞;
  2. 单个请求单个try-catch,否则所有的请求都会当成异常来处理;
  3. 超时后,结果返回null,但要注意服务端可能是完成了;
  4. 带返回值的submit (CallableTask),如果不调用future.get()是不会抛出异常的;
  5. 不带返回值的excute(Runnable)直接抛异常;
  6. @Async注解,当没有返回值的时候,需要自定义异常实现捕获异常,有返回值调用future.get()OK;

7、小结

    为了实现数据的安全性和一致性,有时需要返回线程的执行结果,这里就用到了FutureTask来实现线程的返回值,但是需要注意阻塞和异常的处理,需要设置超时时间和异常必须调用get()捕获。
 
    OK---我自横刀向天笑,去留肝胆两昆仑。
 
 
水滴石穿,积少成多。学习笔记,内容简单,用于复习,梳理巩固。
 
 
 
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值