第19章 Future设计模式(Java高并发编程详解:多线程与系统设计)

1.先给你一张凭据

假设有个任务需要执行比较长的的时间,通常需要等待任务执行结束或者出错才能返回结果, 在此期间调用者只能陷入阻塞苦苦等待, 对此, Future设计模式提供了一种凭据式的解决方案。在我们日常生活中,关于凭据的使用非常多见,比如你去某西服手工作坊想订做一身合体修身的西服,西服的制作过程比较漫长,少则一个礼拜,多则一个月,你不可能一直待在原地等待, 一般来说作坊会为你开一个凭据, 此凭据就是Future, 在接下来的任意日子里你可以凭借此凭据到作坊获取西服。在本章中,我们将通过程序的方式实现Future设计模式, 让读者体会这种设计的好处。

2.Future设计模式实现

Future设计模式所涉及的关键接口和它们之间的关系UML图

2.1接口定义

1.Future接口设计

Future提供了获取计算结果和判断任务是否完成的两个接口, 其中获取计算结果将会导致调用阻塞(在任务还未完成的情况下),相关代码如所示

public interface Future <T>{// 返回计算后的结果,该方法会陷入阻塞状态T get() throws InterruptedException;// 判断任务是否已经执行完成boolean done();
}
2.FutureService接口设计

FutureService主要用于提交任务, 提交的任务主要有两种, 第一种不需要返回值, 第二种则需要获得最终的计算结果。FutureService接口中提供了对FutureServiceImpl构建的工厂方法, JDK8中不仅支持default方法还支持静态方法, JDK 9甚至还支持接口私有方法。FutureService接口的设计代码如所示。

public interface FutureService<IN, OUT> {// 提交不需要返回值的任务,Future.get方法返回的将会是nullFuture<?> submit(Runnable runnable);// 提交需要返回值的任务,其中Task接口代替了Runnable接口Future<OUT> submit(Task<IN,OUT> task, IN input);// 使用静态方法创建一个FutureService的实现static <T,R> FutureService<T,R> newService() {return new FutureServiceImpl<>();}}

3.Task接口设计

Task接口主要是提供给调用者实现计算逻辑之用的, 可以接受一个参数并且返回最终的计算结果, 这一点非常类似于JDK 1.5中的Callable接口, Task接口的设计代码如所示。

@FunctionalInterface
public interface Task<IN,OUT> {// 给定一个参数,经过计算返回结果OUT get(IN input);
}

2.2 程序实现

1.FutureTask

FutureTask是Future的一个实现,除了实现Future中定义的get() 以及done() 方法, 还额外增加了protected方法finish, 该方法主要用于接收任务被完成的通知, FutureTask接口的设计代码如所示。


public class FutureTask <T> implements Future<T> {// 计算结果private T result;// 任务是否完成private boolean isDone = false;// 定义对象锁private final Object LOCK = new Object();@Overridepublic T get() throws InterruptedException {synchronized (LOCK) {// 当任务还没有完成时,调用get方法会被挂起而进入堵塞while(!isDone) {LOCK.wait();}}// 返回最终计算结果return result;}protected void finish(T result) {synchronized (LOCK) {// balking设计模式if ( isDone )return;// 计算完成,为result指定结果,并且将isDone设为true,同事唤醒阻塞中的线程this.result = result;this.isDone = true;LOCK.notifyAll();}}// 返回当前任务是否已经完成@Overridepublic boolean done() {return isDone;}
}

FutureTask中充分利用了线程间的通信wait和notifyAll, 当任务没有被完成之前通过get方法获取结果, 调用者会进入阻塞, 直到任务完成并接收到其他线程的唤醒信号, finish方法接收到了任务完成通知, 唤醒了因调用get而进入阻塞的线程。

2.FutureServiceImpl
import java.util.concurrent.atomic.AtomicInteger;/*
FutureServiceImpl的主要作用在于当提交任务时创建一个新的线程来受理该任务,进而达到任务异步执行的效果*/
public class FutureServiceImpl<IN, OUT> implements FutureService<IN, OUT>{// 为执行的线程指定名字前缀(再三强调,为线程起一个特殊的名字是一个非常好的编程习惯)private final static String FUTURE_THREAD_PREFIX = "FUTURE-";private final AtomicInteger nextCounter = new AtomicInteger(0);private String getNextName() {return FUTURE_THREAD_PREFIX + nextCounter.getAndIncrement();}@Overridepublic Future<?> submit(Runnable runnable) {final FutureTask<Void> future = new FutureTask<>();new Thread(()->{runnable.run();// 任务执行结束之后将null作为结果传给futurefuture.finish(null);},getNextName()).start();return future;}@Overridepublic Future<OUT> submit(Task<IN, OUT> task, IN input) {final FutureTask<OUT> future = new FutureTask<>();new Thread(() -> {OUT result = task.get(input);// 任务执行结束之后,将真实的结果通过finish方法传递给futurefuture.finish(result);}, getNextName()).start();return future;}
}

3.Future的使用以及技巧总结

Future直译是“未来”的意思, 主要是将一些耗时的操作交给一个线程去执行, 从而达到异步的目的,提交线程在提交任务和获得计算结果的过程中可以进行其他的任务执行,而不至于傻傻等待结果的返回。

我们提供了两种任务的提交(无返回值和有返回值)方式,在这里分别对其进行测试。无返回值的任务提交测试如下:

import java.util.concurrent.TimeUnit;public class FutureServiceClient {public static void main(String[] args) throws InterruptedException {// 定义不需要返回值的FutureServiceFutureService<Void, Void> service = FutureService.newService();// submit方法为立即返回的方法Future<?> future = service.submit(()-> {try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("I am finish done.");});// get方法会使当前线程进入阻塞future.get();}
}

测试有返回值的

import java.util.concurrent.TimeUnit;public class FutureServiceClient1 {public static void main(String[] args) throws InterruptedException {// 定义有返回值的FutureServiceFutureService<String,Integer> service = FutureService.newService();// submit方法会立即返回Future future = service.submit( input -> {try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}return input.length();}, "Hello");// get方法使当前线程进入阻塞,最周会返回计算的结果System.out.println(future.get());}
}

4.增强FutureService使其支持回调

使用任务完成时回调的机制可以让调用者不再进行显式地通过get的方式获得数据而导致进入阻塞, 可在提交任务的时候将回调接口一并注入, 在这里对FutureService接口稍作修改,修改代码如所示。

   // 增加回调接口Callback,当任务执行结束之后,Callback会得到执行@Overridepublic Future<OUT> submit(Task<IN, OUT> task, IN input, Callback<OUT> callback) {final FutureTask<OUT> future = new FutureTask<>();new Thread(() -> {OUT result = task.get(input);future.finish(result);// 执行回调if(null != callback)callback.call(result);}, getNextName()).start();return future;}

修改后的submit方法, 增加了一个Callback参数, 主要用来接受并处理任务的计算结果, 当提交的任务执行完成之后, 会将结果传递给Callback接口进行进一步的执行, 这样在提交任务之后不再会因为通过get方法获得结果而陷入阻塞。

Callback接口非常简单, 非常类似于JDK 8中的Consumer函数式接口, Callback接口的代码如所示。

@FunctionalInterface
public interface Callback <T>{// 任务完成后会调用该方法,其中T为任务执行后的结果void call(T t);
}

测试代码:

import java.util.concurrent.TimeUnit;public class FutureServiceClient2 {public static void main(String[] args) throws InterruptedException {// 定义不需要返回值的FutureServiceFutureService<String, Integer> service = FutureService.newService();// submit方法为立即返回的方法Future<?> future = service.submit(input-> {try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}return input.length();},"HelloWorld", System.out::println);// get方法会使当前线程进入阻塞future.get();}}

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

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

相关文章

[Android] 全球网测-版本号4.3.8

[Android] 全球网测 链接&#xff1a;https://pan.xunlei.com/s/VOIV5G3_UOFWnGuMQ_GlIW2OA1?pwdfrpe# 应用介绍 "全球网测"是由中国信通院产业与规划研究所自主研发的一款拥有宽带测速、上网体验和网络诊断等功能的综合测速软件。APP突出六大亮点优势&#xff1a…

判断您的Mac当前使用的是Zsh还是Bash:echo $SHELL、echo $0

要判断您的Mac当前使用的是Zsh还是Bash&#xff0c;可以使用以下方法&#xff1a; 查看默认Shell: 打开“终端”应用程序&#xff0c;然后输入以下命令&#xff1a; echo $SHELL这将显示当前默认使用的Shell。例如&#xff0c;如果输出是/bin/zsh&#xff0c;则说明您使用的是Z…

MYSQL第四次

目录 题目分析 代码实现 一、修改 Student 表中年龄&#xff08;sage&#xff09;字段属性&#xff0c;数据类型由 int 改变为 smallint 二、为 Course 表中 Cno 字段设置索引&#xff0c;并查看索引 三、为 SC 表建立按学号&#xff08;sno&#xff09;和课程号&#xff…

MATLAB | 基于Theil-Sen斜率和Mann-Kendall检验的栅格数据趋势分析

最近看到一些博主分享关于 SenMK 检验的代码&#xff0c;对于新手来说可能有点复杂。我们编写了一段 MATLAB 代码&#xff0c;能够一次性解决这些问题&#xff0c;简化操作流程。我们还准备了几个关于趋势检验的空间分布图&#xff0c;供大家参考。 一、Sens Slope和Mann-Kenda…

72.在 Vue3 中使用 OpenLayers 进行 Drag-and-Drop 拖拽文件解析并显示图形

在 WebGIS 相关的开发中&#xff0c;我们经常需要加载各种地理数据文件&#xff0c;如 GeoJSON、KML、GPX 等。而 OpenLayers 提供了 DragAndDrop 交互组件&#xff0c;使得我们可以通过拖拽方式加载这些文件&#xff0c;并将其中的地理要素渲染到地图上。 本文将详细介绍如何…

VM虚拟机安装群晖系统

下载群晖系统 https://download.csdn.net/download/hmxm6/90351935 安装群晖连接软件 synology-assistant-6.2-24922(在上面的压缩包里面) 准备好VM虚拟机 创建群晖虚拟机 打开下载下来的虚拟机 添加硬盘 选择类型 创建新的磁盘 指定容量 指定存储文件 完成硬盘添加…

瞬态分析中的时域分析与频域分析:原理、对比与应用指南

目录 一、核心概念区分 二、时域分析&#xff1a;时间维度直接求解 1. 基本原理 2. 关键特点 3. 典型算法 4. 应用案例 三、频域分析&#xff1a;频率维度的等效映射 1. 基本原理 2. 关键特点 3. 典型方法 4. 应用案例 四、对比与选择依据 1. 方法论对比 2. 工程…

基于LMStudio本地部署DeepSeek R1

DeepSeek R1 DeepSeek R1是由DeepSeek团队开发的一款高性能AI推理模型&#xff0c;其开源版本包括完整的DeepSeek R1 671B权重&#xff0c;以及基于其蒸馏出的多个小型模型。 DeepSeek R1通过蒸馏技术将推理模式迁移到更小的模型中&#xff0c;显著提升了这些模型的推理能力。…

2.攻防世界 ics-06

题目描述处给出提示 进入题目页面如下 发现只有报表中心能进入下一个页面 页面内容&#xff1a; 发现有传参 改变日期也没有变化 更改id数值页面也没有回显 猜测应该有一个特定id对应的页面即为那一处入侵者留下的数据 下面使用burp suite爆破id值 先用burp suite抓包 右键…

Linux 的使用

补充内容&#xff1a;EasyHPC - Linux基础入门【笔记】 文章目录 文档与教程终端命令 文档与教程 Linux 操作系统目录结构解释 - Linux迷 (linuxmi.com) 一个专注于Linux和开源技术的在线平台&#xff1a;It’s FOSS (itsfoss.com)理解各种命令&#xff1a;explainshell.com -…

机器学习-线性回归(最大似然估计)

机器学习任务可以分为两类: 一类是样本的特征向量 &#x1d499; 和标签 &#x1d466; 之间存在未知的函数关系&#x1d466; h(&#x1d499;)&#xff0c;另一类是条件概率&#x1d45d;(&#x1d466;|&#x1d499;)服从某个未知分布。最小二乘法是属于第一类&#xff0c…

数据完整性与约束的分类

一、引言 为什么需要约束&#xff1f;为了保证数据的完整性。 &#xff08;1&#xff09;数据完整性 数据完整性指的是数据的精确性和可靠性。 为了保证数据的完整性&#xff0c;SQL对表数据进行额外的条件限制&#xff0c;从以下四方面考虑&#xff1a; ①实体完整性&…

autMan奥特曼机器人-对接deepseek教程

一、安装插件ChatGPT 符合openai api协议的大模型均可使用此插件&#xff0c;包括chatgpt-4/chatgpt-3.5-turbo&#xff0c;可自定义服务地址和模型&#xff0c;指令&#xff1a;gpt&#xff0c;要求Python3.7以上&#xff0c;使用官方库https://github.com/openai/openai-pyt…

@[TOC](优先级队列(堆)) 【本节目标】 1. 掌握堆的概念及实现 2. 掌握 PriorityQueue 的使用

优先级队列&#xff08;堆&#xff09; 1. 优先级队列1.1 概念 2. 优先级队列的模拟实现2.1 堆的概念2.2 堆的存储方式2.3 堆的创建2.3.1 堆向下调整2.3.2 堆的创建2.3.3 建堆的时间复杂度 2.4 堆的插入与删除2.4.1 堆的插入2.4.2 堆的删除 2.5 用堆模拟实现优先级队列 【本节目…

【Linux网络编程】之守护进程

【Linux网络编程】之守护进程 进程组进程组的概念组长进程 会话会话的概念会话ID 控制终端控制终端的概念控制终端的作用会话、终端、bash三者的关系 前台进程与后台进程概念特点查看当前终端的后台进程前台进程与后台进程的切换 进程组 进程组的概念 当我们使用以下命令查与…

11.PPT:世界动物日【25】

目录 NO12​ NO34 NO56​ NO789视频音频​ NO10/11/12​ NO12 设计→幻灯片大小→ →全屏显示&#xff08;16&#xff1a;9&#xff09;确定调整标题占位符置于图片右侧&#xff1a;内容占位符与标题占位符左对齐单击右键“世界动物日1”→复制版式→大小→对齐 幻灯片大小…

Java项目: 基于SpringBoot+mybatis+maven+mysql实现的智能学习平台管理系(含源码+数据库+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismavenmysql实现的智能学习平台管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、…

Odoo免费开源ERP最佳业务实践:主生产计划概论

Odoo主生产计划(Master Production Schedule&#xff0c; MPS)是确定每一个具体产品在每一个具体时间段的生产计划。 文&#xff5c;开源智造Odoo亚太金牌服务 老杨 概述 Odoo是全球排名第一的免费开源ERP系统&#xff0c;以其强大的功能和模块化设计著称&#xff0c;适用于各…

TOTP实现Google Authenticator认证工具获取6位验证码

登录遇到Google认证怎么办? TOTP是什么?(Google Authenticator) TOTP(Time-based One-Time Password)是一种基于时间的一次性密码算法,主要用于双因素身份验证。其核心原理是通过共享密钥和时间同步生成动态密码,具体步骤如下: 共享密钥:服务端与客户端预先共享一个…

@RequestBody与@ResponseBody:Spring数据处理的“翻译官”

在Spring中&#xff0c;RequestBody和ResponseBody注解就像是数据交换的“翻译官”。 1. RequestBody注解&#xff1a;它的作用就像是把客户端发来的“外语”翻译成Java对象。当我们发送一个HTTP请求到服务器时&#xff0c;请求体里通常包含了一些数据&#xff0c;这些数据可能…