zoukankan      html  css  js  c++  java
  • FutureTask原理解析

    原文链接:http://www.studyshare.cn/blog/details/1130/1

    首先写一个简单的Demo

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

    FutureTask futureTask =new FutureTask(new Callable() {

            @Override

            public Object call() throws Exception {

                long startTime = System.currentTimeMillis();

                int count =0;

                //进行累加,让子线程执行一段时间

                for(int i=0;i<2000000000;i++){

                    count += i;

                }

                System.out.println("count = " + count +" use time is "+ (System.currentTimeMillis()-startTime));

                return "test";

            }

    });

        new Thread(futureTask).start();

        //get()在主线程中执行,阻塞方法,主线程等待子线程执行完才被唤醒执行获取子线程返回的结果

        System.out.println(futureTask.get());

    }

    打印结果很简单:


     

    首先解释FutureTask的成员变量含义,在它的各种方法中很多地方使用

            volatile int state :用来表示当前执行线程的状态,用volatile关键字修饰,表示内存可见性,其他线程修改volatile修饰的state后,子线程会强         制到主内存取state最新的值,可参考文章:https://www.cnblogs.com/daxin/p/3364014.html

            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;//已中断状态

            private Callable callable : Callable接口变量,外部实例化传递到FutureTask中

            private Object outcome :用于存放返回结果或者异常信息

            private volatile Thread runner :存放当前子线程(执行任务的线程)

            private volatile WaitNode waiters :一个节点内部类,类中包含

            static final class WaitNode {

                    volatile Thread thread;//线程成员变量

                    volatile WaitNode next;//下一个包装了等待线程的节点

                    WaitNode() {thread = Thread.currentThread(); }//初始化时当前线程设置为需要等待的线程

             }

    当前子线程执行过程中以上状态会有以下几种路线:

            1.  NEW -> COMPLETING -> NORMAL : 新建 --> 执行--> 正常结束

            2.  NEW -> COMPLETING -> EXCEPTIONAL:新建--> 执行--> 异常结束(执行过程中发生异常)

            3.  NEW -> CANCELLED : 新建--> 取消(准备执行时主线程调用子线程的取消方法)

            4.  NEW -> INTERRUPTING -> INTERRUPTED :新建--> 被通知中断(准备执行时主线程调用子线程取消方法并传递中断标志位true)-->已中断

    FutureTask的主要方法:

            1.FutureTask(Callable callable):构造方法,初始化传入的callable,并将state设置为NEW

            2.isCancelled() :获取当前执行中的线程状态是否已经取消,已取消返回true,否则返回false

            3.isDone() : 获取当前执行的线程的状态是否处于运行状态,是返回true,否则返回false

            4.cancel(boolean mayInterruptIfRunning):发出取消或者中断当前线程的信号。参数为true,则发出中断,否则为取消

            5.get()、get(long timeout, TimeUnit unit) :获取线程执行完成后的返回结果

            6.set(V v):设置线程的结果值到成员变量outcome 中

            7.setException(Throwable t):设置线程出现异常时的异常值到成员变量outcome 中

            8.run():FutureTask实现了Runnable接口,因此需要覆盖run()方法,call()方法就是在run()方法中进行调用

            9.finishCompletion():该方法是执行的线程完成(无论正常完成还是异常完成)后唤醒其他等待的线程继续执行。并清空callable。

            10.awaitDone(boolean timed, long nanos):核心方法,当执行线程在运行中,主线程调用get()方法后则加入等待队列,类似AQS的同步队列,执行的线程执行完毕会调用finishCompletion()方法唤醒等待队列中的线程进行执行。

    下面对Demo中的代码进行源码分析:

            第一步:执行构造方法

                public FutureTask(Callable callable) {

                        if (callable ==null) //判断传入的callable不为空,则初始化FutureTask中的成员变量

                                throw new NullPointerException();

                        this.callable = callable;

                        this.state =NEW;      // ensure visibility of callable

                }

            第二步:new Thread(futureTask).start();

                此处实例化一个线程并调用start()方法,则操作系统调度该线程,并分配CPU时间片,如果该线程获取到CPU时间片后,则会执行run方法

                注意:由于使用的是FutureTask,该类实现了Runnable接口并覆写了run方法,则会进入到FutrueTask的run方法中执行,代码如下:

                public void run() {

                    //首先判断state是否是处于新建以外的状态(如果是新建以外的状态则直接返回,因为线程刚进入run只会是new状态,为了程序健壮性做此判断),compareAndSwapObject是判断当前FutureTask中的runner是否是null,为空则将当前执行的这个线程赋值给runner,不为空就直接退出

                    if (state !=NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))

                        return;

                    try {

                        Callable c =callable;

                        if (c !=null &&state ==NEW) { //判断状态与callable

                        V result;

                        boolean ran;

                        try {

                            result = c.call(); //执行Callable中的call()方法

                            ran =true;

                        }catch (Throwable ex) {//如果执行call()方法出现了异常,则捕获异常并去设置异常信息

                            result =null;

                            ran =false;

                            setException(ex);//捕获异常并去设置异常信息,将异常信息设置到Object outcome结果对象中,同时改变state的状态为EXCEPTIONAL

                        }

                        if (ran)

                            set(result);//正常执行,设置返回结果,设置到Object outcome中,并设置state状态为NORMAL

                        }

                        }finally {

                            runner =null;//清空runner,方便GC回收

                            int s =state;

                            if (s >=INTERRUPTING) //如果state状态为中断中或者已中断

                                handlePossibleCancellationInterrupt(s);//交出当前线程的执行权,与其他线程重新竞争

                            }

                    }

                此处看一下setException()与set()方法内的源码

                setException():

                    protected void setException(Throwable t) {

                        //原子操作比较state的内存地址上的值是否与NEW相等,相等则将state修改为COMPLETING

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

                            outcome = t;//使用outcome保存异常结果信息

                            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // 将state设置为EXCEPTIONAL

                            finishCompletion();//下面贴出源码,具体解释

                        }

                    }

                set():

                    protected void set(V v) {

                            //原子操作比较state的内存地址上的值是否与NEW相等,相等则将state修改为COMPLETING

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

                                    outcome = v;//使用outcome保存正常返回的结果信息

                                    UNSAFE.putOrderedInt(this, stateOffset, NORMAL);  // 将state设置为NORMAL

                                    finishCompletion();

                            }

                        }

                    finishCompletion():这是线程执行(正常或者异常)的最后一个方法,下面分析一下源码

                        private void finishCompletion() {

                            // 无论执行的线程是正常还是异常都会返回结果并设置到outcome中,那么返回结果后,主线程或者其他线程还在等待队列中,则需要去唤醒等待队列中的线程进行执行。

                            for (WaitNode q; (q =waiters) !=null;) {//循环等待队列中的线程节点

                                if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {//判断等待线程节点是否与之前设置的等待线程一致,一致则返回true

                                    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; // 置空,方便GC回收

                                            q = next;//当前等待线程节点也置空,在上面已经唤醒了当前的等待线程,因此此处也将已经唤醒的线程置空让GC回收

                                        }

                                        break;

                                    }

                                }

                                done();

                                callable =null;        // 当前线程中的callable置空,让gc回收,整个线程到此执行完毕

                            }

                以上详细说明线程start()方法调用后的一系列执行过程。那么有个问题就是这个等待线程节点是从哪里产生的呢?下面解释这个问题

                看Demo中的代码: System.out.println(futureTask.get()); 这句代码在demo有解释,get()方法是一个阻塞的方法,那么就是这个get()

                方法产生的WaitNode节点的。看源码:

                    public V get() throws InterruptedException, ExecutionException {

                        int s =state; 

                        if (s <=COMPLETING) //判断state是否处于执行中或者新建状态,是的话则说明调用get()方法的线程需要进入等待队列,等待callable所在的线程执行完毕后并等待唤醒,此处的代码就证明了get()方法是阻塞的

                            s = awaitDone(false, 0L);//下面分析源码

                        return report(s);

                     }

                    awaitDone():

                        private int awaitDone(boolean timed, long nanos) throws InterruptedException {

                                final long deadline = timed ? System.nanoTime() + nanos :0L;//引入等待超时机制,调用get()方法传false,0L则表示不进行超时处理

                                WaitNode q =null;

                                boolean queued =false;

                                for (;;) {//自旋

                                    if (Thread.interrupted()) {//判断当前线程(主线程)是否已经中断,中断则去移除等待线程节点并抛出中断异常,此代码为程序健壮性考虑,不必过分关注

                                        removeWaiter(q);

                                        throw new InterruptedException();

                                    }

                                    int s =state;

                                    if (s >COMPLETING) {//如果执行的子线程的state状态已经处于NORMAL或者EXCEPTIONAL状态,说明子线程已经执行结束了,那么直接返回,说明不用让阻塞线程进入等待队列。

                                        if (q !=null)//等待线程节点不为空,则直接置为空,并返回state

                                            q.thread =null;

                                            return s;

                                    }

                                    else if (s ==COMPLETING)// 重点,当当前执行子线程还在运行中的时候,则此时要让主线程交出CPU执行权。

                                        Thread.yield();//交出CPU执行权,yield()方法虽然是交出执行权,但主线程还是可以和其他线程进行公平竞争

                                    else if (q ==null)//重点,以上的条件都不满足,则说明该线程确实需要进入等待队列进行等待

                                        q =new WaitNode();//构造一个等待节点,该构造方法中将当前线程作为参数传递给节点中的局部变量进行保存

                                    else if (!queued)//注意,此处较难理解,这是入队列的操作,q.next=waiters ,如果是首节点,waiters一定是null的,则q.next=null,waitersOffset偏移量指向的地址上的初始值也是null,则期望值与内存地址值都为null,则会将q的值设置到waitersOffset指向的地址,同时返回true,等待节点进入队列就成功。原子操作比较抽象,建议去深入理解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);//自旋到这里的时候,说明已经加入等待队列,阻塞当前线程,让其等待被唤醒。

                            }

                        }

                    另外最后获取返回值的方法report()比较简单就不多做解释。内部就是返回outcome的值

                        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源码就分析完毕,总结一下:Callable实现的线程内部使用state的转换,这种转换是基于原子操作来保证线程安全(多线程环境下对state进行竞争),同时其他非Callable的外部线程调用FutureTask中的方法(主要是get()),则让这些线程进入等待队列,当Callable的线程执行完毕会使用自旋对等待中的线程进行唤醒。

    更多深度技术好文:http://www.studyshare.cn/index

  • 相关阅读:
    swoole入门abc
    PHP实现定时任务(非linux-shell方式,与操作系统无关)
    PHP经典算法题
    lumen使用CORS解决跨域问题
    轻松搞定 JS 的this、call和apply
    Nginx与PHP工作原理
    PHP的cURL扩展库使用详解
    PHP 中的 cURL 爬虫实战基础
    jvm误区--动态对象年龄判定
    从源码分析如何优雅的使用 Kafka 生产者
  • 原文地址:https://www.cnblogs.com/darendu/p/9855099.html
Copyright © 2011-2022 走看看