JAVA并发编程系列(13)Future、FutureTask异步小王子

news2024/11/27 5:32:45

美团本地生活面试:模拟外卖订单处理,客户支付提交订单后,查询订单详情,后台需要查询店铺备餐进度、以及外卖员目前位置信息后再返回。

时间好快,一转眼不到一个月时间,已经完成分享synchronized、volatile、CAS、AQS、ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier、并发锁、Condition、线程池、ThreadLocal等多个核心基础原理和案例剖析。

其实我编写文章速度真的很慢,基本每篇文章需要写2~3个小时去梳理。确保基础理论、源码分析、面试案例、优缺点等均分享到位,力求每篇都是干货实用,让每位看到我文章的同学,不管是面试、还是应用到工作实践,都能有所收益。

今天我们围绕Future是什么、怎么用,实践demo来展开分享,实现原理架构来展开。

一、Future是什么

首先我们回到一个问题,就是为什么需要Future、FutureTask?

之前我们用过的线程池ThreadPoolExecutor、线程Thread都可以执行异步任务,但是无法执行带有返回值的异步任务。而Future是可以执行这种带有返回值的异步任务。线程池ThreadPoolExecutor、Thread线程可以通过提交执行Future类型的任务,就可以获取任务返回值。

和Callable、Runnable一样,Future是一个接口。我们看一下它的接口源码。

//Runnable接口,只有一个run方法
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
//Callable接口,只有一个call方法
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

//本文主角Future有5个方法
public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

代码非常少,有5个方法,但是核心方法是get(),还是isDone();

get()方法:用来读取任务的返回结果。此外,如果任务未执行,调用该方法的当前线程会进入阻塞等待。

isDone()方法:检查计算是否已完成。这个方法不会阻塞线程。日常使用Future,一般是先调用isDone()方法判断结果是否返回,然后再调用get()方法获取执行结果。

一句话总结:Future是异步计算任务,提交Future任务后可以继续干别的;等干完别的事,再回来通过get()方法去读取Future任务结果

再简单总结:Future是支持在未来读取结果的异步计算任务。

cancel()方法:用来尝试取消任务,仅仅是尝试,不一定成功。如果任务已经开始执行,那么它就不能被取消。

isCancelled()方法:就是用来检查这个Future任务是否被取消。

Future和之前分享的信号量Semaphore、CountDownLatch倒数门闩、CyclicBarrier循环屏障都不一样,唯一和Condition条件队列有点像,支持多个线程协调进行。支持异步读取任务结果这个特性,Future可以很方便支持多个任务并发执行,以及在最后汇总获取并发结果,最后返回给终端。

二、应用实践:模拟同时查外卖信息

我们用FutureTask实现一个并发面试题:模拟外卖订单处理,客户支付提交订单后,查询订单详情,后台需要查询店铺备餐进度、以及外卖员目前位置信息后再返回。

package lading.java.mutithread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 模拟外卖订单处理,客户支付提交订单后,查询订单详情,后台
 * 通过查询店铺备餐进度、以及外卖员目前位置信息。
 */
public class Demo015Future {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1、异步查询商家系统任务
        FutureTask<Boolean> checkFoodIsReadyTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName()+"转发请求到商家系统,查询餐厅当前订单备餐进度...");
            Thread.sleep(2000);
            boolean foodIsOk = true;
            System.out.println(Thread.currentThread().getName()+"商家接口返回是否备餐完成,结果是:" + foodIsOk);
            return foodIsOk;
        });
        //2、异步查询外卖员系统任务
        FutureTask<Boolean> checkCourierIsReadyTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName()+"转发请求到外卖员系统,查询外卖员是否已到店...");
            Thread.sleep(800);
            boolean courierIsOk = true;
            System.out.println(Thread.currentThread().getName()+"查询外卖员是否已到店,结果是:" + courierIsOk);
            return courierIsOk;
        });
        //并发查询商家、外卖员情况,大约2s
        new Thread(checkFoodIsReadyTask).start();
        new Thread(checkCourierIsReadyTask).start();
        Thread.sleep(100);
        //只是判断是否完成,不会阻塞
        if (!checkFoodIsReadyTask.isDone()) {
            System.out.println(Thread.currentThread().getName() + "查询店铺备餐进度未完成,继续等等...");
        }
        if (!checkCourierIsReadyTask.isDone()) {
            System.out.println(Thread.currentThread().getName() + "查询外卖员情况未完成,继续等等...");
        }
        //如果结果没返回会阻塞等待
        System.out.println(Thread.currentThread().getName() + "线程 成功查到商家备餐结果:" + checkFoodIsReadyTask.get());
        System.out.println(Thread.currentThread().getName() + "线程 成功查到外卖员结果:" + checkCourierIsReadyTask.get());
    }
}

运行结果:

三、硬核干活:FutureTask的实现原理源码分析

我们从它的属性开始,然后讲实现的方法原理。

3.1 FutureTask的属性

FutureTask 的源码也不多,属性就这5个。

线程状态state、执行任务的callable,任务执行的返回结果outCome,正在运行的线程runner,等待队列里的waiters节点。

3.1.1 当前任务线程状态state,以及状态枚举

这七种任务状态之间相互转换关系:

1、正常结束

NEW -> COMPLETING -> NORMAL

2、异常结束

NEW -> COMPLETING -> EXCEPTIONAL

3、任务被取消

NEW -> CANCELLED

4、任务出现中断

NEW -> INTERRUPTING -> INTERRUPTED

//当前任务线程的状态,以下几个枚举值都是state不同状态
private volatile int state;
 
//当前任务线程刚创建状态
private static final int NEW          = 0;
//当前任务线程即将完成,ing是一个即将完成状态
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;
 
3.2 其他属性意义
//Callable 来执行任务,Callable 有返回值的线程
private Callable<V> callable;
 
//Callable任务执行的返回结果
private Object outcome; 
 
//当前正在运行的线程
private volatile Thread runner;
 
//等待队列节点,WaitNode是FutureTask的内部类
private volatile WaitNode waiters;

waiters等待队列:这个是单向队列,里面是等待本任务执行结果的线程。比如ABCD四个线程,其中A线程执行了任务,B、C、D线程,都等A线程的返回结果,就需要在waiters等待队列里等着。

//等待队列源码
    static final class WaitNode {
        //被阻塞的线程,也就
        volatile Thread thread;
        //这里看出它就是个单向队列
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }
3.3 run()方法原理

run方法比较简单,不放源码,直接说,就是执行定义的callable任务,任务执行完成后,通过CAS去更新outCome返回值。

    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

3.4 get()方法原理

这个方法和run()一样重要,其他线程想要获取本任务结果,都是通过get方法读取。逻辑也是很简单。其中waitDone()方法,就是进入等待等列等结果。

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        //1、如果任务没执行完成,当前想读取任务结果的线程就进入阻塞等待
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        //2、如果任务执行完成,就返回结果    
        return report(s);
    }
3.5 其他方法
    //当前任务状态是否被取消,直接读状态值
    public boolean isCancelled() {
        return state >= CANCELLED;
    }
    //当前任务状态是否以及执行结束,直接读状态值
    public boolean isDone() {
        return state != NEW;
    }

本系列文章推荐:

1、JAVA并发编程系列(12)ThreadLocal就是这么简单|建议收藏

2、JAVA并发编程系列(11)线程池底层原理架构剖析

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

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

相关文章

Linux应用——简易日志

1. 日志要求 对于一个日志来说&#xff0c;我们任认为其应该具有以下的内容 1. 日志时间 2. 日志等级 3. 日志内容 4. 文件名称与行号 在此基础上我们对不同的日志做出分级&#xff0c;即 info: 常规信息 warning: 报警信号 error: 严重信号&#xff0c;可能需要立…

UNIAPP 动态菜单实现方法

1. 封装tabbar组件&#xff0c;组件UI使用uview的tabbar allList 定义出全部的菜单 list 定义当前用户能看到的菜单使用 u-tabbar 渲染出来 list 2. 权限判断处理 3. 使用方式 在 tab 页&#xff0c;底部放入该 tab 组件&#xff0c;并设置当前回显的页面&#xff0c;这里使用…

STM32F407寄存器操作(DMA+I2C)

1.前言 因为后面需要用到大量基础通讯传输的问题&#xff0c;于是今天折腾了一下DMA传输I2C与SPI的效果&#xff0c;其实我先是把DMASPI搞出来了。但是考虑到网上对于STM32的I2C微词颇多&#xff0c;基础的协议都没有调试出来&#xff0c;更遑论DMA控制了&#xff0c;前面调不…

排序算法之——归并排序,计数排序

文章目录 前言一、归并排序1. 归并排序的思想2. 归并排序时间复杂度及空间复杂度3. 归并排序代码实现1&#xff09;递归版本2&#xff09;非递归版本 二、计数排序1. 计数排序的思想2. 计数排序的时间复杂度及空间复杂度3. 计数排序代码实现 总结&#xff08;排序算法稳定性&am…

计算机毕业设计 基于Python的无人超市管理系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

深度学习基础—残差网络ResNets

1.残差网络结构 当网络训练的很深很深的时候&#xff0c;效果是否会很好&#xff1f;在这篇论文中&#xff0c;作者给出了答案&#xff1a;Deep Residual Learning for Image Recognitionhttps://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/He_Deep_Residual_…

使用html写一个能发起请求的登录界面

目录 head部分 内联样式部分 body部分 login-form类的div myModal类的div id script部分 总的代码 界面与操作演示 <!DOCTYPE html> <html lang"en"> <!DOCTYPE html> 这是文档类型声明&#xff0c;告诉浏览器这是一个 HTML文档。 <…

jmeter学习(1)线程组与发送请求

1、线程组 执行顺序 &#xff1a;setUp线程组 > 线程组 > tearDown线程组 2、 发送请求 可以发送http、java、dubbo 请求等 下面讲解发送http 1&#xff09;Http请求默认值 作用范围是该线程组下的所有HTTP请求&#xff0c;如果http请求设置的与默认值冲突&#xff0…

前端规范工程-3:CSS规范(Stylelint)

样式规范工具&#xff08;StyleLint&#xff09; Stylelint 是一个灵活且强大的工具&#xff0c;适用于保持 CSS 代码的质量和一致性。结合其他工具&#xff08;如 Prettier 和 ESLint&#xff09;&#xff0c;可以更全面地保障前端代码的整洁性和可维护性。 目录 样式规范工具…

oracle virtualBox 拖动文件到虚拟机内报错

DnD: Error: Drag and drop to guest not possible -- either the guest OS does not support this, or the.... 首先将拖放的双向选项打开 打开CD驱动器 根据操作系统是32还是64安装对应的安装包&#xff0c;amd64为64位系统&#xff0c;x86为32位系统 安装后重启即可向虚拟机…

【C语言】数组练习

【C语言】数组练习 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚练习2、二分查找 练习1&#xff1a;多个字符从两端移动&#xff0c;向中间汇聚 编写代码&#xff0c;演示多个字符从两端移动&#xff0c;向中间汇聚 练习2、二分查找 在⼀个升序的数组中查找指…

sql语句牛客练习

文章目录 1. SQL21 浙江大学用户题目回答情况① 错误② 正确 2. SQL22 统计每个学校的答过题的用户的平均答题数① 错误② 正确 3. SQL23 统计每个学校各难度的用户平均刷题数4. SQL25 查找山东大学或者性别为男生的信息① 错误② 正确 5. SQL26 计算25岁以上和以下的用户数量①…

Wireshark 解析QQ、微信的通信协议|TCP|UDP

写在前面 QQ&#xff0c;微信这样的聊天软件。我们一般称为im&#xff0c;Instant Messaging&#xff0c;即时通讯系统。那大家会不会有疑问&#xff0c;自己聊天内容会不会被黑客或者不法分子知道&#xff1f;这种体量的im是基于tcp还是udp呢&#xff1f;这篇文章我们就来探索…

基于Springboot的在线订餐系统设计与实现(论文+源码)_kaic

摘 要 当今世界&#xff0c;互联网以及和互联网有关的行业都在不断的发展&#xff0c;也在持续走进人们的生活&#xff0c;在此趋势下人们对于通过互联网解决生活问题的需求愈来愈多&#xff0c;本文考虑到了这些情况后做出了该订餐系统。 本系统选择了MySQL作为主要存储单元…

搭建k8s集群服务(kubeadm方式)

准备工作 操作系统版本&#xff1a;CentOS Linux release 7.9.2009 (Core) 虚拟机硬件配置&#xff1a;2核8G内存&#xff08;最低2G&#xff09;&#xff0c;硬盘最低25G&#xff1b; linux内核版本&#xff08;3.10版本尝试失败&#xff09;&#xff1a;5.4.268-1.el7.elr…

每日读则推(三)

n.(事件的)发生地点,(活动的)场所 n.雄性大园丁鸟 n.多细枝的,苗条的 v.放大,扩大(声音);增强,加强 Male great bowerbirds build twiggy concert venues that amplify their raucous songs and n.园丁鸟 …

讲职场:不要经常说消极的话

1、不要经常说消极的话&#xff0c;不要接触让自己力量消失的人 习惯性用强大的语言加持自己&#xff0c;才能立起来 2、只要你下决心钻研一门技术&#xff0c;你就全身心扑在上面&#xff0c;把每一个细节研究透&#xff0c;只有这样&#xff0c;你才能在学会之后&#xff0…

投资精明之选,国内外低代码平台性价比排行榜

本文介绍了国内外10大低代码平台的特点及性价比&#xff0c;包括ZohoCreator、OutSystems等&#xff0c;强调低代码平台通过简化开发过程&#xff0c;提高应用开发效率和质量&#xff0c;适合不同规模企业。选择时考虑企业需求和预算&#xff0c;建议试用后再决策。 一、Zoho C…

Apache安装后无法启动的问题“不能再本地计算机启动apache”

首先安装 参考这位博主的小白下载和安装Apache的教程&#xff08;保姆级&#xff09; 遇到的问题 在启动的时候遇到问题 说apache不能在本地计算机启动 解决方法 1. 路径检查 首先&#xff01;&#xff01;&#xff01; 请仔细检查你的httpd.conf文件中的Apache路径是否…

基于Springboot+Vue的汉服交易小程序的设计与实现(含源码+数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…