Java多线程下载文件
JVM是支持多线程程序的,当程序需要同时执行两个或多个任务,实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等多线程程序比单线程程序更具优势,可充分利用CPU资源,完成时间更短,提高应用程序的响应,增强用户体验。因此学会改善程序结构,将即长又复杂的进程分为多个线程,独立去运行,对于开发者来说至关重要。
以下载多个文件为例,如何使用多线程机制,高效率的完成下载任务?且听我我慢慢道来。
提出需求:编写一个API,打包下载GitHub的所有用户头像(以zip形式返回所有用户头像)。

文件压缩我们统一使用apache的commons-compress相关类进行压缩,因此需要引入相关的依赖
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-compress -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-compress</artifactId><version>1.24.0</version>
</dependency>
完整代码:
/*** TODO** @Description* @Author laizhenghua* @Date 2023/8/31 09:22**/
@RestController
@SpringBootApplication
public class TestApplication {public static void main(String[] args) {SpringApplication.run(TestApplication.class, args);}@Autowiredprivate ServletContext servletContext;@GetMapping("/test")public void test() {ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletResponse response = servletRequestAttributes.getResponse();RestTemplate restTemplate = new RestTemplate();String usersUrl = "https://api.github.com/users";// 查询github用户信息JSONArray userList = restTemplate.getForObject(usersUrl, JSONArray.class);if (CollectionUtils.isEmpty(userList)) {fallback("下载失败,失败原因: 查询为空", response);return;}// 下载路径准备String rootPath = servletContext.getRealPath("/") + "avatars";File root = new File(rootPath);if (!root.exists()) {root.mkdir();}// 初始化线程池(JDK 5.0新增的线程池API更多知识可自行学习)ExecutorService executorService = Executors.newFixedThreadPool(10);userList.forEach(item -> {JSONObject user = new JSONObject((Map)item);String login = user.getString("login"); // github登录名String downloadUrl = user.getString("avatar_url"); // 头像下载地址String filePath = rootPath + File.separator + login + ".png";// 执行下载任务(下载至本地)// ****** 一个线程处理一个用户(主线程只负责提交任务尽可能把耗时逻辑都放到多线程任务里如下载、IO操作等) ******executorService.execute(() -> {try {File file = new File(filePath);boolean newFile = file.createNewFile();if (!newFile) {return;}String name = Thread.currentThread().getName();String log = String.format("[%s] download start --- download path: %s", name, filePath);System.out.println(log);// 调用下载接口获取输入流程ResponseEntity<Resource> responseEntity = restTemplate.getForEntity(downloadUrl, Resource.class);// 将得到的输入流写入文件InputStream inputStream = null;OutputStream outputStream = null;try {inputStream = Objects.requireNonNull(responseEntity.getBody()).getInputStream();outputStream = new FileOutputStream(file);byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, len);}} catch (IOException e) {e.printStackTrace();} finally {IOUtils.close(inputStream);IOUtils.close(outputStream);}} catch (IOException e) {e.printStackTrace();}});});// 关闭线程池executorService.shutdown();// 使用org.apache.commons类压缩下载好的头像ZipArchiveOutputStream zipAos = null;try {// 等待线程池中所有任务执行完成(指定时间内没有执行完则返回false)boolean allTaskCompleted = executorService.awaitTermination(30, TimeUnit.MINUTES);if (!allTaskCompleted) {fallback("下载失败", response);}// 设置下载信息response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode("github_avatar.zip", "utf-8") + "\"");response.setContentType("application/zip");zipAos = new ZipArchiveOutputStream(response.getOutputStream());zipAos.setEncoding("UTF-8");zipAos.setUseZip64(Zip64Mode.AsNeeded);File[] files = root.listFiles(); // 获取所有下载好的头像assert files != null;for (File file : files) {// 将头像压缩至 github_avatar.zip 文件ZipArchiveEntry entry = new ZipArchiveEntry(file, file.getName());entry.setLastModifiedTime(FileTime.fromMillis(file.lastModified()));zipAos.putArchiveEntry(entry);try (InputStream inputStream = new FileInputStream(file)) {byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) != -1) {zipAos.write(buffer, 0, len);}file.delete(); // 删除文件}}zipAos.closeArchiveEntry();} catch (Exception e) {e.printStackTrace();} finally {IOUtils.close(zipAos);}}private void fallback(String message, HttpServletResponse response) {response.setCharacterEncoding("UTF-8");response.setContentType(MediaType.APPLICATION_JSON_VALUE);PrintWriter writer = null;try {R error = R.error(500, message);JSONObject json = new JSONObject(error);writer = response.getWriter();writer.append(json.toString());} catch (IOException e) {e.printStackTrace();} finally {if (writer != null) {writer.close();}}}
}
对于打包下载,我们可以用单线程,也可以用多线程,处理这种任务多线程的优势就体现出来了,可自行对比下单线程和多线程程序响应速度。

使用多线程需要注意的是:
- Executors.newFixedThreadPool()是创建一个可重用固定线程数量的线程池。
- 主线程只负责分配任务,把耗时的逻辑尽可能的写到多线程任务上独立执行。
- 使用完线程池必须要关闭,先调用 shutdown() 方法关闭线程池,然后调用 awaitTermination(long timeout, TimeUnit unit) 方法等待线程池中的所有任务执行完成,只有线程池中的所有任务都执行完了,才能把响应信息写到
response上。
相关文章:
Java多线程下载文件
JVM是支持多线程程序的,当程序需要同时执行两个或多个任务,实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等多线程程序比单线程程序更具优势,可充分利用CPU资源,完成时间更短,提高应用程…...
oracle 同一张表同时insert多条数据 mysql 同一张表同时insert多条数据
oracle 同一张表同时insert多条数据 在Oracle数据库中,你可以使用INSERT ALL语句同时向同一张表插入多条数据。INSERT ALL语句允许你一次执行多个插入操作,可以提高插入的效率和速度。 以下是使用INSERT ALL语句插入多条数据的示例: INSERT…...
ROS键盘遥控机器人,通过参数服务器指定速度
1、引言 在上节的驱动机器人,我们知道是cmd_vel话题发布一串Twist类型消息来控制,我们可以输入如下命令查看这个Twist的详细信息:rosmsg show geometry_msgs/Twist geometry_msgs/Vector3 linear float64 x float64 y float64 z geome…...
具有快表的地址变换机构
1.快表(TLB) 快表,又称联想寄存器(TLB,translation lookaside buffer), 是一种访问速度比内存快很多的高速缓存(TLB不是内存! ), 用来存放最近访问的页表项的副本,可以加速地址变换的速度。 与…...
【使用python和flask建个人博客】修复侧边栏最新文章、最多阅读等链接不能打开的问题
自从上次因版本兼容问题修改过部分代码之后,好长时间没光顾woniunote这个个人博客模块了,最近发文章的时候发现侧边栏的文章打不开,定位了bug,并进行了修复。 <div class="col-12 side"><div class="tip" align...
ShareX使用说明——优秀的录屏软件
ShareX初识 ShareX 是一个自由及开放源代码的截图录像软件,目前仅支持Windows系统。 项目源代码在GitHub平台上发布, 软件可以在Microsoft商店和Steam上下载。 ShareX is a free and open source program that lets you capture or record any area of y…...
10.14~10.15verilog操作流程与Block Design
后面的那个是延时精度 verilog文件结构 文件名称与写的模板没有关系,这个文件名为P1,但模板名为andgate 但是如果是仿真文件,就需要开头的模板名和仿真文件名相同 .v是源文件,设计文件 .v在设计与sim里都有,静态共享࿰…...
小解C语言文件编译过程【linux】
小解C语言文件编译过程【linux】 库动态库静态库 C语言文件 程序编译过程整体预处理编译汇编链接动态链接静态链接两种方法对比 库 看到标题是文件编译过程 但是开头却是库,这可不是挂羊头卖狗肉,而是因为库也是代码不可缺少的一部分,并且在…...
[Python]黑色背景白色块滑动视频
黑色背景白色块滑动视频,单帧效果如下: 配置参数 1920 1080 400 400 300 60 1920x1080.avi import numpy as np import cv2 as cv import os import syswidth 1920 height 1080 rect_szx 400 rect_szy 300 sz_y_init 400 fps 24width int(sys.a…...
【linux kernel】对linux内核设备的注册机制和查找机制分析
文章目录 1、简介2、device_initialize分析3、device_add分析4、总结 🔺【linux内核系列文章】 👉对一些文章内容进行了勘误,本系列文章长期不定时更新,希望能分享出优质的文章! 1、《linux内核数据结构分析之哈希表》…...
asp.net酒店餐饮管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
一、源码特点 asp.net酒店餐饮管理系统是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语言 开发 ASP.NE 酒店餐饮管理系统 二、功能…...
38_Nginx 启动流程
文章目录 src/core/nginx.cint ngx_cdecl main(int argc, char *const *argv) {ngx_buf_t *b;...
数据特征选择 | Lasso特征选择(Python)
文章目录 效果一览文章概述源码设计小结效果一览 文章概述 Lasso算法是一种经典的线性回归算法,被广泛应用于特征选择和降维问题。相较于传统的线性回归算法,Lasso算法能够在保持预测准确性的同时,自动筛选出对目标变量影响较大的特征变量,从而达到降低模型复杂度、提高泛化…...
最小覆盖子串[困难]
优质博文:IT-BLOG-CN 一、题目 给你一个字符串s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串,则返回空字符串"" 。 对于t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量…...
保姆级搭建Mysql 并进行视图可视化操作
安装MySQL数据库 选择mysql5.7.36_x32.msi”,双击运行,如下图所示: 在此窗口中,选择“Custom”选项,点击“Next>”进入下一步; 在此窗口中,选择号下的MySQL Server 5.7.36 – x64&…...
设计模式的学习顺序
设计模式的学习顺序可以按照以下步骤进行: 掌握基础知识:先确保你对编程语言和软件开发的基本概念有深入的理解,包括面向对象编程、继承、多态等。学习常用设计模式:首先学习并理解一些常用的设计模式,例如单例模式、…...
数据结构和算法——树结构
二叉树 又叫二叉排序树。 节点是数量为,,n为层数。 满二叉树:所有的叶子节点都在最后一层。 完全二叉树:如果所有叶子节点都在最后一层和倒数第二层,而且每个叶子节点都有左右子节点。 完全二叉树 前序遍历 1、先输…...
【Java】Integer包装类
Integer:对基本数据类型 int 实现包装 方法名称说明public Integer(int value)根据 int 值创建 Integer 对象(JDK9以后过时)public integer(String s)根据 String 值创建 Integer 对象…...
Web后端开发登录校验及JWT令牌,过滤器,拦截器详解
如果用户名正确则成功进入 登录功能 代码 Controller Service Mapper 结果 若登录成功结果如下: 如果登录失败,结果如下 登录校验 为什么需要登录校验 有时再未登录情况下, 我们也可以直接访问部门管理, 员工管理等功能 因此我们需要一个登录校验操作, 只有确认用户登录…...
大语言模型迎来重大突破!找到解释神经网络行为方法
前不久,获得亚马逊40亿美元投资的ChatGPT主要竞争对手Anthropic在官网公布了一篇名为《朝向单义性:通过词典学习分解语言模型》的论文,公布了解释经网络行为的方法。 由于神经网络是基于海量数据训练而成,其开发的AI模型可以生成…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...
