Java多线程下载文件

news2025/1/24 17:47:39

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);
    }

    @Autowired
    private 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();
            }
        }
    }
}

对于打包下载,我们可以用单线程,也可以用多线程,处理这种任务多线程的优势就体现出来了,可自行对比下单线程和多线程程序响应速度。

在这里插入图片描述

使用多线程需要注意的是:

  1. Executors.newFixedThreadPool()是创建一个可重用固定线程数量的线程池。
  2. 主线程只负责分配任务,把耗时的逻辑尽可能的写到多线程任务上独立执行。
  3. 使用完线程池必须要关闭,先调用 shutdown() 方法关闭线程池,然后调用 awaitTermination(long timeout, TimeUnit unit) 方法等待线程池中的所有任务执行完成,只有线程池中的所有任务都执行完了,才能把响应信息写到response上。

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

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

相关文章

代码管理工具 gitlab实战应用

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…

Dokcer基础操作命令演示

Docker中的常见命令&#xff0c;可以参考官方文档&#xff1a;https://docs.docker.com/engine/reference/commandline/cli/ 1、命令介绍 其中&#xff0c;比较常见的命令有&#xff1a; 命令说明文档地址docker pull拉取镜像docker pulldocker push推送镜像到DockerRegistr…

【大数据】Apache Hive数仓(学习笔记)

一、数据仓库基础概念 1、数仓概述 数据仓库&#xff08;数仓、DW&#xff09;&#xff1a;一个用于存储、分析、报告的数据系统。 OLAP&#xff08;联机分析处理&#xff09;系统&#xff1a;面向分析、支持分析的系统。 数据仓库的目的&#xff1a;构建面向分析的集成化数据…

【Eclipse】Plug-in Development 插件的安装

先按路线找到需要的页面&#xff1a;eclipse–Window–Preferences–Java–Editor–Content Assist 在Work with框中输入&#xff1a;http://download.eclipse.org/releases/2019-06 PS&#xff1a;后面的2019-06是eclipse发行的时间 选择&#xff1a;General Purpose Tools 下…

TwinCAT3添加伺服控制器的方法

文章目录 一. 主要参考二. 使用嵌入式控制器CX5140添加三菱MR-J4-TM伺服控制器的方法具体步骤 一. 主要参考 《TwinCAT NC 使用指南》第四章 TwinCAT NC 控制总线伺服 二. 使用嵌入式控制器CX5140添加三菱MR-J4-TM伺服控制器的方法 嵌入式控制器: 倍福CX5140伺服控制器&…

【Eclipse】解决插件下载速度太慢

解决方案&#xff1a;修改镜像 下面列出几个国内的镜像网站&#xff1a; 中国科学技术大学(5.6MB/s) http://mirrors.ustc.edu.cn/eclipse/ 北京理工大学&#xff08;600KB/s&#xff09; http://mirror.bit.edu.cn/eclipse/ 大连东软信息学院(400KB/s) http://mirrors.neuso…

入门小白拥有服务器的建议

学习网络知识 当我们拥有了一台服务器以后,需要提前准备学习一些网络、服务器、互联网方便的知识, 以备在后续学习工作中使用。 建议的网络知识学习清单: 1. 网络基础知识:包括网络拓扑结构、协议、IP地址、子网掩码、网关等基础概念。 2. 网络安全:包括网络攻击类型、防…

基于黑猩猩优化的BP神经网络(分类应用) - 附代码

基于黑猩猩优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于黑猩猩优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.黑猩猩优化BP神经网络3.1 BP神经网络参数设置3.2 黑猩猩算法应用 4.测试结果&#x…

英语——语法——从句——定语从句——笔记

一、从句的定义&#xff08;Clause&#xff09; 英语的语法中&#xff0c;有主句和从句之分。主句呢&#xff1f;当然是&#xff0c;我们要表达主要意思的句子&#xff0c;要表达的所有句子都可简化为某某是/做什么。 而从句的概念&#xff0c;指复合句中不能独立成句&#xf…

PyTorch入门教学——简介与环境配置

一、简介 PyTorch是一个开源的Python机器学习库&#xff0c;其前身是2002年诞生于纽约大学的Torch。它是美国Facebook公司使用python语言开发的一个深度学习的框架。特点&#xff1a; 简洁 PyTorch的设计追求最少的封装&#xff0c;尽量避免重复造轮子。 简洁的设计带来的另外…

SpringMVC之国际化上传下载

spring项目中的国际化 1&#xff09;提供中英两种资源文件 i18n_en_US.properties i18n_zh_CN.properties 2&#xff09;配置国际化资源文件&#xff08;在spring配置文件中添加&#xff0c;例如spring-mvc.xml&#xff09; <bean id"messageSource" class&quo…

CSS小技巧之单标签loader

本文翻译自 How to create a CSS-only loader with one element&#xff0c;作者&#xff1a; Temani Afif&#xff0c; 略有删改。 loader组件是网站的重要组成部分。它可以用在许多地方&#xff0c;我们需要显示的内容正在加载中。这样的组件需要尽可能简单&#xff0c;在这篇…

Go语言入门心法(四): 异常体系

Go语言入门心法(一): 基础语法 Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 Go语言入门心法(四): 异常体系 Go语言入门心法(五): 函数 一: go异常体系认知 后期补充完整,忙着去耍帅,泡妹子去.............

C++之继承<1>【详解】

C之继承【详解】 1. 继承的概念2. 继承定义的格式2.1 继承方式和访问限定符2.2 继承了基类成员后随着访问方式变化的变化 3. 基类和派生类对象的复制转换5. 隐藏 1. 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在…

日志分析题目

关卡描述&#xff1a;1、黑客在内网抓了两台肉鸡对服务器进行攻击&#xff0c;请给出两台肉鸡的IP&#xff08;使用空格分隔&#xff09; 直接查看日志 rh 然后ls&#xff0c;查看 这里我没有看到明显web容器的字样&#xff0c;例如apache&#xff0c;iis的等等。 就看容器…

《华为战略管理法:DSTE实战体系》作者谢宁老师受邀为某电力上市集团提供两天的《成功的产品管理及产品经理》内训。

​​ 近日&#xff0c;《华为战略管理法&#xff1a;DSTE实战体系》作者谢宁老师受邀为某电力上市集团提供两天的《成功的产品管理及产品经理》内训。 谢宁老师作为华为培训管理部特聘资深讲师和顾问&#xff0c;也是畅销书《华为战略管理法&#xff1a;DSTE实战体系》、《智慧…

贪心算法(一)

&#x1f58a;作者 : D. Star. &#x1f4d8;专栏 : 算法小能手 &#x1f606;今日提问 : 国庆去哪里打卡了呢&#xff1f; &#x1f606;今日分享 : 武功山风景打卡–双云海 文章目录 &#x1f33b;贪心算法的思想&#x1f33b;贪心算法的基本思路&#x1f4d6;给大家讲一个小…

高可用eureka服务注册与发现代码例子

代码 Eureka server 1 pom.xml <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.clou…

Python开源项目周排行 2023年第36周

#2023年第36周2023年10月14日1书生・浦语灵笔图文混合创作大模型。基于书生・浦语大语言模型研发的视觉 - 语言大模型&#xff0c;提供出色的图文理解和创作能力&#xff0c;具有多项优势&#xff1a; 图文交错创作: 浦语・灵笔可以为用户打造图文并貌的专属文章。生成的文章文…

Go语言入门心法(五): 函数

一: go语言函数认知 Go语言入门心法(一): 基础语法 Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 忙着去耍帅,后期补上..........