FutureTask 源码解析

为了更好的提供文章,我已经将博客迁移到了自建的博客网站上,我将更多的从源码分析的角度入手,为大家带来更多的深度文章,请大家继续关注我~!  博客地址: www.liuinsect.com
_______________________________________________________________________________

 站在使用者的角度,future是一个经常在多线程环境下使用的Runnable,使用它的好处有两个:
1. 线程执行结果带有返回值
2. 提供了一个线程超时的功能,超过超时时间抛出异常后返回。
 
那,怎么实现future这种超时控制呢?来看看代码:


 
FutureTask的实现只是依赖了一个内部类Sync实现的,Sync是AQS (AbstractQueuedSynchronizer)的子类,这个类承担了所有future的功能,AbstractQueuedSynchronizer的作者是大名鼎鼎的并发编程大师Doug Lea,它的作用远远不止实现一个Future这么简单,后面在说。
 
下面,我们从一个future提交到线程池开始,直到future超时或者执行结束来看看future都做了些什么。怎么做的。
首先,向线程池ThreadPoolExecutor提交一个future:

  ThreadPoolExecutor将提交的任务用FutureTask包装一下:


 

 
然后尝试将包装后的Future用Thread类包装下后启动,

红色标记的地方表示,当当前线程池的大小小于corePoolSize时,将任务提交,否则将该任务加入到workQueue中去,如果workQueue装满了,则尝试在线程数小于MaxPoolSize的条件下提交该任务。


 
顺便说明下,我们使用线程池时,常常看到有关有界队列,无界队列作为工作队列的字眼:使用无界队列时,线程池的大小永远不大于corePoolSize,使用有界队列时的maxPoolSize才有效,原因就在这里,如果是
无界队列,红框中的add永远为true 下方的addIfUnderMaximumPoolSize怎么也走不到了,也就不会有线程数量大于MaxPoolSize的情况。
 
言归正传,看看addIfUnderCorePoolSize 中做了什么事:
new了一个Thread,将我们提交的任务包装下后就直接启动了


 
我们知道,线程的start方法会调用我们runnable接口的run方法,因此不难猜测FutureTask也是实现了Runnable接口的


 

 
  FutureTask的run()方法中是这么写:

 
innerRun方法先使用原子方式更改了一下自己的一个标志位state(用于标示任务的执行情况)
然后红色框的方法 实现回调函数call的调用,并且将返回值作为参数传递下去,放置在一个叫做result的泛型变量中,
然后future只管等待一段时间后去拿result这个变量的值就可以了。   至于怎么实现的“等待一段时间再去拿” 后面马上说明。


 
innerSet在经过一系列的状态判断后,最终将V这个call方法返回的值赋值给了result



 
说到这里,我们知道,future是通过将call方法的返回值放在一个叫做result的变量中,经过一段时间的等待后再去拿出来返回就可以了。
怎么实现这个 “等一段时间”呢?
 
要从Sync的父类AbstractQueuedSynchronizer这个类说起:
 
我们知道AbstractQueuedSynchronizer 后者的中文名字叫做 同步器,顾名思义,是用来控制资源占用的一种方式。对于FutureTask来说,“资源”就是result,线程执行的结果。思路就是通过控制对result这个资源的访问来决定是否需要马上去取得result这个结果,当超时时间未到,或者线程未执行结束时,是不能去取result的。当线程正常执行结束后,一系列的标志位会被修改,并告诉等待future执行结果的各个线程,可以来获取result了。
 
这里会涉及到 独占锁和共享锁的概念。
 
独占锁:同一时间只有一个线程获取锁。再有线程尝试加锁,将失败。 典型例子 reentrantLock
共享锁:同一时间可以有多个线程获取锁。 典型例子,本例中的FutureTask
 
为什么说他们?因为Sync本质上就是想完成一个共享锁的功能,所以Sync继承了AbstractQueuedSynchronizer 所以Sync的方法使用的是AbstractQueuedSynchronizer的共享锁的API
 
首先,我们明白,future结束有两种状态:
     1. 线程正常执行完毕,通知等待结果的主线程对应于future.get()方法。
     2. 线程还未执行完毕,等待结果的主线程已经等不到了(超时),抛出一个TimeOutException后不再等待。对应于future.get(long timeout, TimeUnit unit)
 
下面我们依次看看对于这两种状态,我们是怎么处理的:
从上图中可以得知,线程在执行完毕后会将执行的结果放到result中, 红色框中同时提到了releaseShared 方法,我们从这里进入AbstractQueuedSynchronizer 
 


 
当result已经被赋值,或者FutureTask为cancel状态时,FutureTask会尝试去释放共享锁(可以同时有多个线程调用future.get() 方法,也就是会有多个线程在等待future执行结果,而furue在执行完毕后会依次唤醒各个线程)
如果尝试成功,则开始真正的释放锁, 这里是AbstractQueuedSynchronizer 比较精妙的地方, “尝试”动作都定义为抽象方法,交个各个子类去定义“尝试成功的含义” 而真正的释放则自己实现,这种复杂规则交个子类,流程交给自己的思路很值得借鉴。

 
再看FutureTask的 “尝试释放”的规则:
没啥好说,怎么尝试都成功。


 
接着AbstractQueuedSynchronizer 开始了真正的释放唤醒工作:
 private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases. This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;//把头元素取出来,保持头元素的引用,防止head被更改
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {//如果状态位为:需要一个信号去唤醒  注释原话:/** waitStatus value to indicate successor's thread needs unparking */
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) //修改状态位
                        continue; // loop to recheck cases
                    unparkSuccessor(h);//如果修改成功,则通过头元素找到一个线程,并且唤醒它(唤醒动作是通过JNI方法去调用的)
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue; // loop on failed CAS
            }
            if (h == head) // loop if head changed
                break;
        }
    }
 
循环遍历后,知道已经没有结点需要唤醒则返回,依次return后,future的run方法执行完毕。
 
 
以上是针对future线程的,我们知道,FutureTask已经将执行结果放在了result中,并且按等的先后顺序依唤醒了等待队列上的线程。
那,猜测future.get方法就不难了,对于带超时的get方法:最大的可能性就是不断的检查future的一个状态位,看它是否执行完毕,执行完则获取结果返回,否则,再阻塞自己一段时间。
对于不待超时的,就上来就先尝试获取结果,拿不到就阻塞自己,直到上述的innerSet方法唤醒它。
究竟是不是这样呢?一起来看看:



 



 
因为innerGet(long nanosTimeout) 和innerGet()流程大致相同,所以我们重点讲解innerGet(long nanosTimeout) ,在唯一一个有区别的地方说明下即可。
 
如下图所示,对于innerGet(long nanosTimeout) 方法,FutureTask采用的方法是直接加锁或者每隔一段时间尝试加锁,如果成功,则返回true,则如上图所示,直接返回result,主线程拿到执行结果。
否则,抛出超时异常。


 
对于tryAcquireShared 方法,比较简单,直接看future是否执行完毕


 
如果没有结束,则进入doAcquireSharedNanos方法:
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {

        long lastTime = System.nanoTime();
        final Node node = addWaiter(Node.SHARED);//在队列尾部增加一个结点,我的理解是,用来标明这个队列是共享者队列还是独占队列
        try {
            for (;;) {
                final Node p = node.predecessor();//拿出刚才新增结点的前一个结点:实际有效的队尾结点。
                if (p == head) {
                    int r = tryAcquireShared(arg);//尝试获取锁。
                    if (r >= 0) {//
                        setHeadAndPropagate(node, r);//返回值大于1 对于FutureTask代表任务已经被cancel了,则更改队列头部结点。
                        p.next = null; // help GC  将p结点脱离队列,帮助GC
                        return true;//返回true后 上述中可以知道当前线成会抛出超时异常 确定下会不会唤醒其他节点?
                    }
                }
                if (nanosTimeout <= 0) { //如果设置的超时时间小于等于0 则取消获取锁
                    cancelAcquire(node);
                    return false;
                }
                if (nanosTimeout > spinForTimeoutThreshold && //等待的时间必须大于一个自旋锁的周期时间
                    shouldParkAfterFailedAcquire(p, node))   //  遍历队列,找到需要沉睡的第一个节点
                    LockSupport.parkNanos(this, nanosTimeout); // 调用JNI方法,沉睡当前线程
                long now = System.nanoTime();
                nanosTimeout -= now - lastTime;   // 更新等待时间  循环遍历
                lastTime = now;   
                if (Thread.interrupted())
                    break;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
        // Arrive here only if interrupted
        cancelAcquire(node);
        throw new InterruptedException();
    }
 
这样通过AQS的协作,所有调用future.get(long timeout, TimeUnit unit)的线程都会按顺序等待,直到线成执行完被唤醒或者超时时间到 主动抛出异常。
 
总结
 
至此为止FutureTask的解析已经基本结束了,可以看到。它依靠AQS的共享锁实现了对线程执行结果的访问控制。和我们通常意义上的访问控制(并发访问某个资源,获取失败时,沉睡自己等待唤醒或者超时后返回)基本是一致的,不外乎维护了一个等待资源的列表。将等待资源的线程通过链表的方式串了起来。
 
当然AQS的功能远不仅如此,它还提供了一套独占锁的API,帮助使用者实现独占锁的功能。
最常用的Reentrantlock就是使用这套API做的。
有机会的话再和大家分享下它的实现。
Probability and Statistics - 4th edition 立即下载

相关推荐

scratch航天梦主题课程作品分享,包含ppt、素材包

而今年多国都将发射探测器尝试登陆月球,2023年或将称为“登月年”,请大家发挥创新思维,以“星辰大海”为创作主题,搜集相关信息 、展开联想,为实现升空 、探测 、取样贡献创意!即使去淘宝买的素材,也要从百度网盘,但素材太多大了或者太多文件,也要充值百度网盘超级会员才能保存,提高下载速度。同学们大家好,今天我们就要开始航天主题-嫦娥五号探月工程系列的编程课程了,不仅可以学习到很多航天知识,了解本次嫦娥五号的发射历程,还能让我们用编程遨游太空呢。即使下载了,也要上传,不是特别方便,尤其是不好查找。

少儿编程吕口口 2484

FutureTask源码深度解析

写在前面:阅读本文前必须对Java多线程知识有所了解。本文将深度解析FutureTask的源码,包括其怎么执行、怎么使调用get的线程阻塞、怎么解除这些线程阻塞、怎么取消以及中断。 建议将这个类作为学习多线程的第一站,还是能学到不少东西的。

qq_54616363的博客 919

Win7/Win10双系统兼容指南:Arduino IDE 1.8.19与2.3.2的安装避坑全攻略

本文详细解析了在Win7/Win10双系统环境下安装和配置Arduino IDE 1.8.19与2.3.2版本的完整指南,特别针对ESP32开发环境的深度配置和COM端口常见问题提供了实用解决方案。通过版本对比、安装优化和性能调优,帮助开发者高效搭建稳定的物联网开发环境。

weixin_30500105的博客 187

FutureTask源码解析

Java创建线程一般用Thread,Runable。如果需要返回结果,就用Callable。Callable运行需要配合Future。Future是一个接口,一般用FutureTask实现类去接收Callable任务返回的结果。FutureTask是同步非阻塞执行的任务,他不会主动通知你结果。

qq_36037795的博客 765

实战指南:如何用DCCRN模型提升语音增强效果(附代码示例)

本文提供了基于DCCRN模型进行语音增强的实战指南。通过深入解析复数网络如何协同处理语音的幅度与相位信息,并附有完整的PyTorch代码实现,帮助开发者从数据准备、模型搭建到训练调优,构建高效的实时语音增强系统,有效提升嘈杂环境下的语音清晰度。

weixin_29054245的博客 479

YOLOv11半监督学习深度解析:基于FixMatch改进的先进目标检测框架

本文探讨了半监督学习与YOLOv11结合的创新方法。针对目标检测中半监督学习面临的伪标签质量不稳定、特征一致性难保证等挑战,研究提出改进版FixMatch框架。通过分析YOLOv11架构特性(如多尺度特征提取、双标签分配策略等)与半监督学习的适配性,设计了三项关键技术改进:空间感知阈值策略、多任务解耦处理和记忆增强机制。该方法在少量标注数据下可实现接近全监督的性能,显著降低工业检测等场景的数据标注成本。

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 2085

java futuretask 源码解析_FutureTask源码解析(2)——深入理解FutureTask

前言有了上一篇对预备知识的了解之后,分析源码就容易多了,本篇我们就直接来看看FutureTask的源码。本文的源码基于JDK1.8。Future和Task在深入分析源码之前,我们再来拎一下FutureTask到底是干嘛的。人如其名,FutureTask包含了Future和Task两部分。我们上一篇说过,FutureTask实现了RunnableFuture接口,即Runnable接口和Future...

weixin_39677538的博客 300

30天入门 SAP BTP 开发系列之四:SAP BTP 控制台里 Global Account 的常规操作介绍

笔者前一篇文章,介绍了 SAP BTP 中 Global Account 的概念。本文咱们一起来看看 SAP BTP 控制台里,给 Global Account 开放了哪些操作功能。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 870

JUC源码解析——FutureTask源码解析

JUC源码解析值FutureTask源码解析,FutureTask是整个线程池执行的主要任务类

程序猿小河 736

2026软考中级-软件设计师笔记(送最新视频教程)

核心口诀:运控存输输(运算器、控制器、存储器、输入、输出)例题(2019年上半年):核心公式:数字签名流程:数字证书作用:死锁4个必要条件:死锁判断公式:处理策略:程序设计语言的四大核心成分:顺序表操作(时间复杂度):链表操作(时间复杂度):核心思想:频率高的字符用短编码,频率低的用长编码 → 最小化总编码长度构造步骤(贪心算法):前提:有序表(升序/降序) 步骤:时间复杂度:O(log₂n) 例:查找中核心思想:用哈希函数将关键字映射到存储位置 哈希冲突:不同关键字映射到同一位置 → 用链地址法或开放定址

36万+

大疆A型板使用经验分享(二)——A型板原理图和引脚图

一.原理图 二.引脚图

weixin_43361652的博客 7684

FutureTask的核心源码解析

摘要:FutureTask是Java并发编程中用于获取线程返回值的工具类,实现了RunnableFuture接口,兼具Runnable和Future特性。其核心在于状态管理(NEW、COMPLETING、NORMAL等状态流转)和CAS操作保证线程安全。通过run方法执行Callable任务,set/setException处理正常/异常结果,cancel方法支持任务中断。源码分析展示了其线程安全设计和状态转换机制,适用于需要获取异步任务执行结果的场景。

MirGao23的博客 224

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

FutureTask源码解析二

本篇主要介绍FutureTask源码 我们知道FutureTask实现了RunnableFuture接口,即Runnable接口和Future接口,Runable可以对应FutureTask的task,表示FutureTask本质上也是一个task任务,而Future对应FutureTask中的Future,表示了我们对于这个task任务可以执行某些操作,如判断任务是否执行完毕,获取任务的执行结果...

大佬请赐教 552

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

FutureTask的底层源码解析

run方法是任务执行的核心逻辑。首先,它检查当前状态是否为NEW,并尝试将当前线程设置为运行者。如果状态不符合条件或设置失败,方法将直接返回。接下来,如果可调用的任务不为null且状态仍为NEW,则执行该任务并捕获可能的异常。如果任务成功执行,结果将被设置;如果发生异常,则会记录该异常。最后,在finally块中,清空runner以防止并发调用run方法,并重新检查状态以处理可能的取消中断。这种设计确保了任务的安全执行和异常处理。

xweiran的博客 1135

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

java futuretask 源码解析_Java异步编程——深入源码分析FutureTask

Java的异步编程是一项非常常用的多线程技术。之前通过源码详细分析了ThreadPoolExecutor《你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识》。通过创建一个ThreadPoolExecutor,往里面丢任务就可以实现多线程异步执行了。但之前的任务主要倾向于线程池,并没有讲到异步编程方面的内容。本文将通过介绍Executor+Future框架(Futu...

weixin_30340109的博客 82

FutureTask源码解析(2)——深入理解FutureTask

Future和Task 在深入分析源码之前,我们再来拎一下FutureTask到底是干嘛的。人如其名,FutureTask包含了Future和Task两部分。 FutureTask实现了RunnableFuture接口,即Runnable接口和Future接口。其中Runnable接口对应了FutureTask名字中的Task,代表FutureTask本质上也是表征了一个任务。而Future接...

qq_42499188的博客 630

FutureTask源码解析一

考虑到文章篇幅过长,分为两篇讲解 本片主要介绍FutureTask涉及的一些接口和基本知识 FutureTask简介 FutureTask是一种可取消的异步计算任务,它实现了Future接口,代表了异步任务的返回结果。从而FutureTask可以启动和取消异步计算任务、查询异步计算任务是否完成和获取异步计算任务的返回结果。只有异步计算任务结束时才能获取返回结果,当异步计算任务还未结束时调用get...

大佬请赐教 307

线程池源码解析1.FutureTask源码解析

FutureTask源码解析

KKK 827
上一篇: 从线程间通信看到的关于基本类型封包的问题
下一篇: 博客迁移
iteye_10121
博客等级 码龄8年 0粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值