• 并发编程学习笔记(11)----FutureTask的使用及实现


    1. Future的使用

      Future模式解决的问题是。在实际的运用场景中,可能某一个任务执行起来非常耗时,如果我们线程一直等着该任务执行完成再去执行其他的代码,就会损耗很大的性能,而Future接口就是Future的实现,它可以让当前线程将任务交给Future去执行,然后当前线程就可以去干别的事,知道耗时任务执行完成之后,当前线程直接获取结果即可。FutureTask的使用比较简单,只需要先实例化一个Callable对象,重写call方法,再创建一个FutureTask对象,将Callable对象传给FutureTask,创建一个线程,将FutureTask对象传入Thread对象,启动线程即可,接下来就直接上源码:

    package com.company.thread.t5;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class FutureDemo {
    
        public static void main(String[] args) {
    
            Callable<Integer> callable = new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    System.out.println("正在计算结果");
                    Thread.sleep(3000);
                    return 1;
                }
            };
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            Thread thread = new Thread(futureTask);
            thread.start();
            //做点别的
            System.out.println("干点别的");
            try {
                System.out.println("拿到的结果为:" + futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    2. FutureTask源码

      FutureTask实现了RunnableFuture,而RunnableFuture同时继承了Runnable和Future接口,所以它既是一个任务,也实现了Future接口的功能。

      从上面使用的步骤来看,我们需要先实例化一个Callable对象,所以先看看Callable接口

    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传给FutureTask的构造方法,所以我们先看FutureTask的构造方法:

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

      如果callable为空,则抛出异常,否则执行赋值操作,并将state状态改成NEW,即是初始化状态,这里说一下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;

      所以初始化时state的值为0,表示当前为初始化状态。

      FutureTask实现了Runnable,所以必然又run方法去供线程使用,接下来就看FutureTask重写的run()方法:

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

      第一步判断state是否为初始化状态,不为初始化状态将将当前线程设置为FutureTask的执行线程,设置失败停止方法,经过第一步还能往下执行,则当前的state必然是初始化状态,得到Callable实例,及真正需要执行的任务代码,判断是否为空,且状态是否为初始化状态,判断成功,则执行callable的call()方法,并将执行结果赋值给result,将执行标记ran赋值为ture,执行成功时,ran为true时调用set()方法保存结果,

      set()方法:

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

      将状态从NEW偏移到COMPLETING(正在执行中),将结果保存到outcome中,(outcome是一个Object类型,不止可以保存结果,还可以保存异常信息),保存完成后将偏移量改为NORMAL(正常执行完毕的状态),调用finishCompletion()方法。

      finishCompletion()方法:

     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
        }

      因为在执行过程中可能会未执行完成就调用了get方法,此时get方法会将想要获取结果的线程都等待在一个等待队列中,所以finishCompletion()的功能就是遍历等待队列,并将等带队列中所有的有效线程唤醒(unpark)。此时等待get()的线程将会继续执行,

      当调用call方法出现异常时,result未空,ran赋值未false,调用setException()方法;

      setException(Throwable t):

     protected void setException(Throwable t) {
            if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
                outcome = t;
                UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
                finishCompletion();
            }
        }

      首先更改状态为COMPLETING,将异常信息保存到outcome中,更改状态为EXCEPTIONAL(表示异常执行结束),调用finishCompletion()唤醒等待线程。run执行完成;

      FutureTask还有一个重要的方法就是get()方法,用于获取Callable的call方法返回的结果或执行过程中出现的异常信息。

      get():

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

      当状态小于等于COMPLETING时,表示当前任务还没有处于终止状态,即可能正在初始化或者是正在执行中,调用awaitDone()方法将线程放入等待队列,并等待起来,直到被唤醒,最后返回report(s)执行结果,

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

      如果正常执行结束,返回执行结果,s >= CANCELLED表示线程不可用状态,抛出异常CancellationException,否则就是异常执行结束,抛出ExecutionException(),这样就可以拿到执行结果。

      FutureTask其核心的原理也是等待/唤醒,当调用get方法没有结果时,线程等待,当set()方法赋值成功,表示任务执行完成,有值可以返回时,唤醒等待的线程,get()方法上等待的线程被唤醒,继续执行,最后返回结果。

      

  • 相关阅读:
    提问回顾
    个人阅读作业+个人总结
    结对项目-数独程序扩展
    个人作业-Week 3
    个人作业-Week 2
    个人项目-数独程序
    个人作业-Week 1
    第0次博客作业
    2017[BUAA软工]第0次个人作业
    [2017BUAA软工]提问回顾
  • 原文地址:https://www.cnblogs.com/Eternally-dream/p/9755358.html
Copyright © 2020-2023  润新知