【Java】浅析FutureTask的核心方法get

前言

        在进行多线程编程时,我们离不开两个重要的任务接口:RunnableCallable。一个线程想要运行,首先它得知道它的任务是什么(它要做什么),而这两个接口恰好是用于表示一个线程需要执行的任务。

        Runnable和Callable两个接口都是任务接口,它们之间有何不同呢?Runnable中的run方法是没有返回值的,而Callable中的call方法有返回值V(泛型);同时call方法支持抛出异常,一般情况下我们都会使用Runnable接口,当需要线程的执行结果时就使用Callable接口。

public interface Runnable {
    
    public abstract void run();
}

public interface Callable<V> {
    
    V call() throws Exception;
}

        那么我们如何获取一个线程的执行结果呢?此时就需要用到Future接口及其实现类FutureTask了。Future接口中有一个get()方法,用于同步获取线程执行结果,同步表示当线程还没有执行完任务时,调用get()方法获取执行结果的线程会阻塞,直至线程返回执行结果。

FutureTask源码解读

        1、FutureTask不直接实现Future接口,而是实现了一个RunnableFuture<V>接口,RunnableFuture<V>接口又继承自Runnable、Future接口,属于这两个接口的子接口,所以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();
}

        2、FutureTask中的字段:

    //任务的执行状态(get方法的关键)
    private volatile int 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;

    
    //任务
    private Callable<V> callable;
    
    //任务执行结果
    private Object outcome; 
    
    //执行任务的线程
    private volatile Thread runner;
    
    //链表结构:保存等待线程
    private volatile WaitNode waiters;

        2.1、WaitNode具体实现:

static final class WaitNode {

        //结点存储的线程
        volatile Thread thread;
        
        //结点的next指针
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

        3、FutureTask的构造方法:

//需要传递一个Callable接口的实现类
//在FutureTask作为任务提交给线程时,执行的是实现类实现的call方法
public FutureTask(Callable<V> callable) {
        
        //如果入参的Callable为null,抛出异常
        if (callable == null)
            throw new NullPointerException();
        //不然赋值给成员变量
        //并将FutureTask状态设置为NEW
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }


/**
    需要传递两个参数:Runnable、Result
    前者是FutureTask作为任务提交给线程时,线程的执行逻辑
    后者是线程在任务完成时,需要get方法返回的结果
    result入参可以为null,表示不需要给定的结果
*/
public FutureTask(Runnable runnable, V result) {
        
        //callable方法会将runnable、result封装成一个callable
        this.callable = Executors.callable(runnable, result);
        //将FutureTask状态设置为NEW
        this.state = NEW;       // ensure visibility of callable
    }

//将Runnable封装成Callable返回
public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }


//RunnableAdapter实现了Callable,所以也可以作为任务交给线程执行。
static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

        4、获取执行结果的get()方法:

public V get() throws InterruptedException, ExecutionException {
    
        int s = state;
        
        if (s <= COMPLETING)
            //如果任务是刚被创建(NEW)、或者是正在处理(COMPLETING)状态
            //说明任务还未被处理完毕,阻塞获取任务执行结果的线程
            s = awaitDone(false, 0L);

        //如果任务是其它状态,说明任务已经处理完毕
        //report方法会根据任务状态返回结果给调用get方法的线程
        return report(s);
    }

        4.1、report()方法:

private V report(int s) throws ExecutionException {
        Object x = outcome;
        
        
        if (s == NORMAL)
            //任务状态为NORMAL,正常返回结果
            return (V)x;
        if (s >= CANCELLED)
            //任务状态为CANCELLED、INTERRUPTING、INTERRUPTED其中之一
            //抛出CancellationException异常
            throw new CancellationException();

        //任务状态为EXCEPTIONAL,抛出ExecutionException异常
        throw new ExecutionException((Throwable)x);
    }

        4.2、awaitDone()方法:

private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        //deadline变量赋值分两种情况
        //调用了get()方法:deadline = 0
        //调用了get(long timeout,TimeUnit unit)方法:deadline = 当前时间 + timeout
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            //如果当前线程被打断了
            //就将其从等待链表中移除,并抛出InterruptedException异常
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }
            
            //获取当前任务的状态
            int s = state;
            
            //任务已完成(正常完成、取消、打断都算完成)
            if (s > COMPLETING) {
                //如果有线程在等待,就将线程设置为null,防止内存溢出
                if (q != null)
                    q.thread = null;
                //返回任务状态
                return s;
            }
            //任务正在处理
            //当前等待执行结果的线程调用yield方法让os将CPU时间片分给其它线程
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            
            //任务刚被创建,处于NEW状态
            //若等待节点q为null,则创建一个等待节点
            else if (q == null)
                q = new WaitNode();
            
            //如果当前等待节点还未加入等待队列,则通过CAS操作将其加入等待队列
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
            //如果设置了超时时间,则计算等待时间
            //1.等待时间 >= 超时时间,那么将等待节点移除,并返回任务状态
            //2.等待时间 < 超时时间,那么就阻塞:超时时间 - 等待时间
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            //如果没有设置超时时间,通过LockSupport无时间限制的阻塞当前线程
            else
                LockSupport.park(this);
        }
    }

        4.3、get方法流程图:

get方法测试:

public class FutureTaskTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        
        //通过匿名内部类的形式实现call方法
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {

                //模拟线程执行任务耗时
                Thread.sleep(3000);

                return "task is completed!";
            }
        };

        FutureTask<String> task = new FutureTask<>(callable);

        long begin = System.currentTimeMillis();
        //将任务task分配给worker线程
        Thread worker = new Thread(task);
        worker.start();

        //主线程获取worker线程的执行结果
        String result = task.get();
        long end = System.currentTimeMillis();
        System.out.println("等待" + (end - begin) + "ms后获取到执行结果:" + result);
    }
}

        执行结果:

补充:

        在分析FutureTask构造方法时,第二个构造方法很有意思。RunnableAdapter只负责组合Runnable,并实现Callable接口,call()方法的逻辑由作为成员变量的Runnable实现,这里有点适配器模式的味道。如下图所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/209221.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vmware安装Centos7

CentOs7镜像文件下载 centos7 镜像文件下载-CSDN博客 配置虚拟机 打开Vmware&#xff0c;点击新建虚拟机 典型安装与自定义安装 典型安装&#xff1a;VMware会将主流的配置应用在虚拟机的操作系统上&#xff0c;对于新手来很友好。 自定义安装&#xff1a;自定义安装可以针…

【Python表白系列】如何实现爱心光波的表白效果(完整代码)

文章目录 爱心光波环境需求完整代码详细分析系列文章爱心光波 环境需求 python3.11.4PyCharm Community Edition 2023.2.5pyinstaller6.2.0(可选,这个库用于打包,使程序没有python环境也可以运行,如果想发给好朋友的话需要这个库哦~)【注】 python环境搭建请见:https://w…

如何下载IEEE出版社的Journal/Conference/Magazine的LaTeX/Word模板

当你准备撰写一篇学术论文或会议论文时&#xff0c;使用IEEE&#xff08;电气和电子工程师协会&#xff09;的LaTeX或Word模板是一种非常有效的方式&#xff0c;它可以帮助你确保你的文稿符合IEEE出版的要求。无论你是一名研究生生或一名资深学者&#xff0c;本教程将向你介绍如…

【C/PTA —— 13.指针2(课内实践)】

C/PTA —— 13.指针2&#xff08;课内实践&#xff09; 一.函数题6-1使用函数实现字符串部分复制6-2 拆分实数的整数部分和小数部分6-3 存在感 二.编程题7-1 单词反转 一.函数题 6-1使用函数实现字符串部分复制 void strmcpy(char* t, int m, char* s) {int len 0;char* ret …

Python过滤掉特定区域内的矩形框

Python过滤掉特定区域内的矩形框 前言前提条件相关介绍实验环境过滤掉特定区域内的矩形框方法一&#xff1a;直接法&#xff08;for循环遍历&#xff09;代码实现输出结果 方法二&#xff1a;列表推导式代码实现输出结果 前言 由于本人水平有限&#xff0c;难免出现错漏&#x…

Vue2+echarts 实现图表的简单绘制

Echarts是一个基于JavaScript的开源可视化库&#xff0c;由百度开发和维护。它通过简单的配置方式&#xff0c;就可以实现各种复杂的数据可视化和图表展示。Echarts支持多种图表类型&#xff0c;包括柱状图、折线图、饼图、散点图、漏斗图等&#xff0c;同时还支持地图可视化和…

zabbix6.4.0配置邮件及企微机器人群聊告警

一、邮件告警 根据公司邮箱自行配置&#xff0c;电子邮件、用户账号密码填自己的邮箱账号密码 动作本次使用的默认的&#xff0c;如果为了更加美观可自行修改。 二、企业微信机器人告警 首先在企微上创建群聊&#xff0c;之后添加群聊机器人 将地址复制&#xff0c;后面用 …

0Ω电阻最大过流能力及作用用途

0Ω电阻最大过流能力及作用用途 0Ω电阻过流能力0Ω电阻的作用 0Ω电阻过流能力 0Ω电阻不一定是真正的0Ω电阻&#xff0c;0Ω电阻存在一定的阻值偏差&#xff0c;主要看生产电阻厂商做哪种了。厂商都是根据电阻标准文件 EN60115-2&#xff0c; 里头0Ω电阻实际最大阻值有 10…

五、关闭三台虚拟机的防火墙和Selinux

目录 1、关闭每台虚拟机的防火墙 2、关闭每台虚拟机的Selinux 2.1 什么是SELinux

Visual Studio2022创建Windows服务程序

文章目录 Visual Studio2022创建Windows服务程序打开工具创建新项目创建成功重命名服务添加安装程序编写逻辑生成程序安装服务打开服务启动服务停止服务卸载服务修改项目配置重新生成安装服务启动服务 Visual Studio2022创建Windows服务程序 打开工具 创建新项目 创建成功 重命…

【翻译】直流电动机的控制

直流电&#xff08;DC&#xff09;电机由于其转矩易于控制&#xff0c;速度控制范围广&#xff0c;已广泛应用于可调速驱动或可变转矩控制中。然而&#xff0c;直流电机有一个主要的缺点&#xff0c;即它们需要机械装置&#xff0c;如换向器和刷子来连续旋转。这些机械部件需要…

改进YOLO5:结合CVPR2023最新 PConv |包含 YOLOv5 / YOLOv8 模型 YAML 文件

改进YOLO5:结合CVPR2023最新 PConv |包含 YOLOv5 / YOLOv8 模型 YAML 文件 一、论文总结PConv模块优势二、YOLOv51. yaml文件2. common代码文件三、YOLOv81. yaml2. modules文件添加3. Task文件4. 测试

播放器开发(七):音视频同步实现

目录 学习课题&#xff1a;逐步构建开发播放器【QT5 FFmpeg6 SDL2】 原理 简单分析&#xff1a; 下图简单描述了在一个播放过程中&#xff0c;假设我们先播放音频&#xff0c;对比一个公共时间轴&#xff0c;视频就会始终比音频慢0.003s。 我们在日常中用一些播放器播放视频…

41 - 如何使用缓存优化系统性能?

缓存是我们提高系统性能的一项必不可少的技术&#xff0c;无论是前端、还是后端&#xff0c;都应用到了缓存技术。前端使用缓存&#xff0c;可以降低多次请求服务的压力&#xff1b;后端使用缓存&#xff0c;可以降低数据库操作的压力&#xff0c;提升读取数据的性能。 今天我…

基于springboot+vue的点餐系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

基于SpringBoot校园周边美食探索及分享平台的设计与实现

摘要&#xff1a; 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域&#xff0c;传统的美食业进而也面临着巨大的挑战&#xff0c…

[二分查找]LeetCode1964:找出到每个位置为止最长的有效障碍赛跑路线

本文涉及的基础知识点 二分查找算法合集 作者推荐 动态规划LeetCode2552&#xff1a;优化了6版的1324模式 题目 你打算构建一些障碍赛跑路线。给你一个 下标从 0 开始 的整数数组 obstacles &#xff0c;数组长度为 n &#xff0c;其中 obstacles[i] 表示第 i 个障碍的高度…

uni-app 微信小程序之自定义圆形 tabbar

文章目录 1. 自定义tabbar效果2. pages新建tabbar页面3. tabbar 页面结构4. tabbar 页面完整代码 1. 自定义tabbar效果 2. pages新建tabbar页面 首先在 pages.json 文件中&#xff0c;新建一个 tabbar 页面 "pages": [ //pages数组中第一项表示应用启动页&#xff…

如何快速选出一支好股票?

俗话说得好&#xff1a;股票选得好&#xff0c;收益少不了&#xff01;不用多说&#xff0c;相信大伙儿都知道选一支好股票究竟有多重要。 但是选股可不像咱们去菜市场买菜一样&#xff0c;看着顺眼就成。选股&#xff0c;其实是一个专业性特别强的技术活儿。 目前最常用的选股…

vscode如何在没有网络的情况下安装插件

vscode如何在没有网络的情况下安装插件 start 遇到没有网络的电脑&#xff0c;无法直接从插件市场安装vscode的插件。写一下 vscode 插件离线安装的方法. 解决方案 目标电脑没有可以安装插件的网络&#xff0c;那我们只能在有网络的环境下载好我们的插件。然后拷贝软件到无…