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

}

打印结果很简单:

更多深度技术文章,在这里。 java开发工具下载地址及安装教程大全,点这里

首先解释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的线程执行完毕会使用自旋对等待中的线程进行唤醒。

更多深度技术文章,在这里。 java开发工具下载地址及安装教程大全,点这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343