FutureTask源码解析

news2024/10/1 15:26:09

1、介绍

Java创建线程一般用Thread,Runable。如果需要返回结果,就用Callable。Callable运行需要配合Future。

Future是一个接口,一般用FutureTask实现类去接收Callable任务返回的结果。FutureTask是同步非阻塞执行的任务,他不会主动通知你结果。

1.1 使用方式

如下图,创建一个FutureTask丢到线程里去执行,通过get获取结果,如果没有执行完成get方法会阻塞线程,一直等到执行完成。

public static void main(String[] args) throws ExecutionException, InterruptedException {
    FutureTask<String> futureTask = new FutureTask<>(()->{
        Thread.sleep(2000);
        return "异步任务";
    });

    Thread t = new Thread(futureTask);
    t.start();
    System.out.println(futureTask.get());

}

2、FutureTask源码解析(查看注释)

2.1 FutureTask属性

/* 
* Possible state transitions: 
* NEW -> COMPLETING -> NORMAL          任务正常执行,正常返回结果 
* NEW -> COMPLETING -> EXCEPTIONAL     任务正常执行,但返回结果异常 
* NEW -> CANCELLED                     任务直接被取消流程 
* NEW -> INTERRUPTING -> INTERRUPTED   任务中断 
*/ 
private volatile int state; private static final int NEW = 0; // 任务的初始状态 
private static final int COMPLETING = 1;    // Callable的结果(正常或者异常结果)正在封装给当前的FutureTask 
private static final int NORMAL = 2;        // 任务正常结束 
private static final int EXCEPTIONAL = 3;   // 执行任务时,发生了异常 
private static final int CANCELLED = 4;     // 任务被取消了 
private static final int INTERRUPTING = 5;  // 线程的中断状态被设置为了true(还在运行) 
private static final int INTERRUPTED = 6;   // 线程被中断了 
/** The underlying callable; nulled out after running */ 
//当前执行的任务 
private Callable<V> callable; 
/** The result to return or exception to throw from get() */ 
// 存放返回的结果或者异常信息,get()返回 
private Object outcome; // non-volatile, protected by state reads/writes 
/** The thread running the callable; CASed during run() */ 
//执行任务的线程 
private volatile Thread runner; 
/** Treiber stack of waiting threads */ 
//单向链表,存放通过get方法挂起等待的线程 
private volatile WaitNode waiters; 




// Unsafe mechanics 
private static final sun.misc.Unsafe UNSAFE; 
//不同属性在对象内存的偏移量 
private static final long stateOffset; 
private static final long runnerOffset; 
private static final long waitersOffset; 
static { 
    try { 
        UNSAFE = sun.misc.Unsafe.getUnsafe(); 
        //通过直接操作对象内存(通过内存偏移量确定位置)设置对象属性值 
        Class<?> k = FutureTask.class; 
        stateOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("state")); 
        runnerOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("runner")); 
        waitersOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("waiters")); 
    } catch (Exception e) { 
        throw new Error(e); 
    } 
}

2.2 FutureTask的run方法

FutureTask的继承链。FutureTask–>RunnableFuture–>Runnable, Future。FutureTask重写了Runable的run()方法。

通过run方法执行的call方法

// run方法执行流程,最终会执行Callable的call方法 
public void run() { 
    //保证任务状态是NEW才可以运行,通过CAS操作将当前线程设置为runner 
    if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) 
        return; 
    try { 
        //要执行的任务 c 
        Callable<V> c = callable; 
        //判断任务不为空,并且状态是NEW 
        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设置为空 
        runner = null; 
        //拿到状态 
        int s = state; 
        //中断后续处理 
        if (s >= INTERRUPTING) 
            handlePossibleCancellationInterrupt(s); 
    } 
} 
protected void set(V v) { 
    //CAS操作,将state从NEW改为COMPLETING状态 
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { 
        //将结果复制给outcome 
        outcome = v; 
        //状态修改为NORMAL,正常结束 
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state 
        finishCompletion(); 
    } 
}

2.3 get()获取结果

public V get() throws InterruptedException, ExecutionException { 
    int s = state; 
    //没有完成 
    if (s <= COMPLETING) 
        //尝试挂起线程,等待拿结果 
        s = awaitDone(false, 0L); 
    return report(s); 
}

2.4 awaitDone等待完成

该方法判断当前线程执行状态:

  • 已经结束,直接返回状态,在方法调用处会返回结果
  • 正在包装结果中,让出时间片等待一会
  • 还在NEW状态,使用头插法将当前线程进入等待队列(这里是读取结果的线程等待对接,如果多个线程需要读取结果,他们将进入队列等待,队列是单链表结构
  • 根据时间判断线程是否需要挂起
private int awaitDone(boolean timed, long nanos) throws InterruptedException { 
    //计算deadline,如果是get(),就是0 ,如果是get(time, unit)就追加系统当前时间 
    final long deadline = timed ? System.nanoTime() + nanos : 0L; 
    //构建WaitNode 
    WaitNode q = null; 
    //是否进了队列 
    boolean queued = false; 
    for (;;) { 
        //这个get的线程是否中断 
        if (Thread.interrupted()) { 
            //将当前节点从waiters移除 
            removeWaiter(q); 
            //并抛出异常 
            throw new InterruptedException(); 
         } 
         int s = state; 
         //判断任务是否已经执行结束 
         if (s > COMPLETING) { 
             //如果设置过WaitNode,直接移除WaitNode的线程(这里是一个死循环,节点在下面创建,但是循环之后可能又回到这里) 
             if (q != null) 
                 q.thread = null; 
             return s; 
          } 
          //COMPLETING状态,任务结果返回中... 
          else if (s == COMPLETING) // cannot time out yet 
          //COMPLETING状态持续时间非常短,只要做一下让步即可 
          Thread.yield(); 
          //<COMPLETING的只有NEW状态,现在线程状态是NEW,(call方法还没有执行完成,准备挂起线程) 
          else if (q == null) 
              q = new WaitNode(); 
              //如果没有进队列,将当前节点插入队首 
          else if (!queued) 
              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 
          //如果无限挂起时间,并且线程还没有结束,继续挂起(get()挂起线程的方式) 
              LockSupport.park(this); 
    }
}

2.5 任务状态变成NORMAL,做后续处理

任务完成后,需要将等待队列的线程一个个唤醒,唤醒的线程将拿到任务执行的结果,并通过GC回收节点

/** 
  * Removes and signals all waiting threads, invokes done(), and 
  * nulls out callable. 
*/ 
private void finishCompletion() { 
    // assert state > COMPLETING; 
    for (WaitNode q; (q = waiters) != null;) { 
        //拿到第一个节点后,直接CAS将其设置为null 
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { 
            for (;;) { 
            //基于q拿到线程信息 
                Thread t = q.thread; 
                if (t != null) { 
                    //将q的线程设置为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         
}

2.6 返回结果

//任务结束 
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); 
}

3、FutureTask存在的问题

  • 问题1: FutureTask获取执行结果前,主线程需要通过get()方法一直阻塞等待子线程执行完成call方法,才可以拿到返回结果
  • 问题2:如果不通过get挂起线程,通过while循环,不停的判断任务的状态是否结束,结束后,再拿结果。如果任务长时间没有执行完毕,CPU会一直调度查看任务状态的方法,浪费CPU资源。

FutureTask是一个同步非阻塞处理任务的方式。

需要一个异步非阻塞处理任务的方式。

CompletableFuture在一定程度上提高了各种异步非阻塞的方案,并且响应式变成,代码编写效果上,效率更高。CompletableFuture也实现了Future接口,可以不使用FutureTask,直接只用CompletableFuture。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型部署——NVIDIA NIM 和 LangChain 如何彻底改变 AI 集成和性能

DigiOps与人工智能 人工智能已经从一个未来主义的想法变成了改变全球行业的强大力量。人工智能驱动的解决方案正在改变医疗保健、金融、制造和零售等行业的企业运营方式。它们不仅提高了效率和准确性&#xff0c;还增强了决策能力。人工智能的价值不断增长&#xff0c;这从它处…

Ubuntu网卡配置

一、低阶版本配置网卡步骤:(如Ubuntu 16.04.2 LTS) 编辑配置文件interfaces,添加网卡配置信息 我这边以root用户登录进服务器,就不需要普通用户每次在命令前添加sudo vim /etc/network/interfaces 1.动态获取ip设置: auto ens3 # 网卡设备名称ens3 iface ens3 ine…

Tdesign TreeSelect 树形选择 多选

这里写自定义目录标题 小程序原生开发 Tdesign TreeSelect 树形选择 多选可以选择不同一级分类下的数据 小程序原生开发 Tdesign TreeSelect 树形选择 多选可以选择不同一级分类下的数据 TreeSelect 树形选择 在原demo基础上修改 const chineseNumber 一二三四五六七八九十.…

音视频入门基础:FLV专题(9)——Script Tag简介

一、SCRIPTDATA 根据《video_file_format_spec_v10_1.pdf》第75页到76页&#xff0c;如果某个Tag的Tag header中的TagType值为18&#xff0c;表示该Tag为Script Tag&#xff08;脚本Tag&#xff0c;又称Data Tag、SCRIPTDATA tag&#xff09;。这时如果Filter的值不为1表示未加…

昇思MindSpore进阶教程--使能图算融合

大家好&#xff0c;我是刘明&#xff0c;明志科技创始人&#xff0c;华为昇思MindSpore布道师。 技术上主攻前端开发、鸿蒙开发和AI算法研究。 努力为大家带来持续的技术分享&#xff0c;如果你也喜欢我的文章&#xff0c;就点个关注吧 正文开始 图算融合是MindSpore特有的网络…

十二生肖国庆姓氏专属头像

关注▲洋洋科创星球▲领取十二生肖国庆姓氏专属头像定制&#xff01; 庆祝祖国75周年华诞&#xff0c;在这个举国欢庆的国庆节时刻&#xff0c;我们特别为您准备了一份独特的礼物——十二生肖国庆姓氏专属头像定制。 十二生肖&#xff0c;又称属相&#xff0c;是中国传统文化中…

Linux 安装redis主从模式+哨兵模式3台节点

下载 https://download.redis.io/releases/ 解压 tar -zxvf redis-7.2.4.tar.gz -C /opt chmod 777 -R /opt/redis-7.2.4/安装 # 编译 make # 安装&#xff0c; 一定是大写PREFIX make PREFIX/opt/redis-7.2.4/redis/ install配置为系统服务 cd /etc/systemd/system/主服务…

盒子是什么? -- 第四课

文章目录 前言一、盒子是什么&#xff1f;二、元素介绍1.边框 - border2.内边距 - padding3. 外边距属性 -- margin 三、拓展知识1.块元素垂直外边距的合并2. 嵌套块元素垂直外边距的合并 四、背景属性五、元素的浮动1.浮动2.清除浮动3.元素定位4. 特殊定位 -- 黏性定位5. z-in…

Python selenium库学习使用实操二

系列文章目录 Python selenium库学习使用实操 文章目录 系列文章目录前言一、模拟登录二、表单录入 前言 在上一篇文章中&#xff0c;我们完成Selenium环境的搭建&#xff0c;和简单的自动化。今天继续深入学习。今天的目标是完成模拟登录&#xff0c;和表单录入。 一、模拟登…

什么是网络准入控制系统?2024年有哪些好用的网络准入控制系统?

网络准入控制系统&#xff08;Network Access Control, NAC&#xff09;是一种网络安全解决方案&#xff0c;旨在确保只有符合特定安全策略的设备和用户才能访问网络资源。NAC系统通过在设备连接到网络之前对其进行身份验证、授权和健康状态检查&#xff0c;从而防止未经授权的…

YOLOv11改进 | 注意力篇 | YOLOv11引入GAM注意力机制

1.GAM介绍 摘要&#xff1a;为了提高各种计算机视觉任务的性能&#xff0c;人们研究了各种注意机制。然而&#xff0c;现有的方法忽略了保留通道和空间信息以增强跨维交互的重要性。因此&#xff0c;我们提出了一种通过减少信息减少和放大全球交互表示来提高深度神经网络性能的…

vue3 实现拖拽排序效果 sortablejs

效果图 依赖安装 npm i sortablejs -S <template><div class"warp"><div class"parent-box" v-for"pItem in sortData" :key"pItem.name"><h2 class"parent-name">{{ pItem.name }}</h2>&l…

程序计数器(学习笔记)

程序计数器是一块较小的内存空间&#xff0c;它的作用可以看做是当前线程所执行的字节码的信号指示器&#xff08;偏移地址&#xff09;&#xff0c;Java编译过程中产生的字节码有点类似编译原理的指令&#xff0c;程序计数器的内存空间存储的是当前执行的字节码的偏移地址 因为…

唱响红色志愿,赞歌献给祖国——杭州建德市庆祝中华人民共和国成立75周年联欢盛宴纪实

作者&#xff1a;华夏之音/李望 通讯员&#xff1a;王江平 9月30日上午&#xff0c;金桂的香气与红旗的鲜艳交相辉映&#xff0c;杭州建德市党群服务中心、建德市新时代文明实践中心内洋溢着一股浓厚的节日氛围。在这里&#xff0c;一场名为“唱响红色志愿、赞歌献给祖国”的联…

企业架构系列(15)ArchiMate第13节:战略视角

战略视角提供了对企业高层战略方向和构成的不同视角建模&#xff0c;使建模者能够专注于某些特定方面。 一、战略视角概览 战略视角主要包括&#xff1a; 战略视角&#xff1a;提供企业战略、能力、价值流和资源以及预期成果的高层概述。能力地图视角&#xff1a;提供企业能力…

MySQL基础篇 part1

为什么使用数据库和数据库基本概念 想在vscode用markdown了&#xff0c;为什么不直接拿pdf版本呢&#xff1f; DB:数据库(Database) 即存储数据的“仓库”&#xff0c;其本质是一个文件系统。它保存了一系列有组织的数据。 DBMS:数据库管理系统(Database Management System)…

Oracle控制文件全部丢失如何使用RMAN智能恢复?

1.手动删除所有控制文件模拟故障产生 2.此时启动数据库发现控制文件丢失 3.登录rman 4.列出故障 list failure; 5.让RMAN列举恢复建议 advise failure; 6.使用RMAN智能修复 repair failure;

当AI遇上金融科技,创新业务场景和案例涌现

大家好&#xff0c;我是Shelly&#xff0c;一个专注于输出AI工具和科技前沿内容的AI应用教练&#xff0c;体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具&#xff0c;拥抱AI时代的到来。 在这个信息爆炸的时代&#xff0c;我们每天…

【路径规划】使用 RRT、RRT* 和 BIT* 进行网格图的路径规划

摘要 本文比较了三种路径规划算法&#xff1a;快速随机树&#xff08;RRT&#xff09;、快速随机树星&#xff08;RRT* &#xff09;和批量信息树&#xff08;BIT*&#xff09;&#xff0c;在网格图环境中进行路径规划的效果。通过仿真分析这些算法在路径质量、计算效率和收敛…

程序员哪里累了?

程序员是最不累的&#xff0c;最不辛苦的职业&#xff0c;非要说有什么门槛&#xff0c;那只需要你有点智力而已。 在这么多的职业中&#xff0c;比程序员轻松的职业可不多&#xff0c;跟程序员的比起来&#xff0c;大部分的职业更苦、更累。 这些问题经常在网上谈论来谈论去&…