CompletableFuture之批量上传
前言
最近接到一个需求,批量上传图片到服务器及实时更新上传进度。当处理大量文件上传任务时,效率是一个关键因素。传统的串行方式会导致任务耗时较长,而使用并发处理可以极大地提高上传效率。想到很久之前用CompletableFuture优化过一些多统计的业务场景,效果都还不错,因此在这里也使用它来优化一下上传的效率。
CompletableFuture简介
CompletableFuture类是Java 8引入的,它实现了Future和CompletionStage接口,提供了更强大和灵活的异步编程功能。CompletableFuture除了具有Future的特性外,还提供了更多的操作和组合方式来处理异步任务。它可以更方便地处理异步任务,实现并发编程,并提供更好的异常处理和结果转换机制。在进行异步编程时,CompletableFuture是一个更为强大和推荐的选择。
主要特点:
异步执行:允许将任务提交给后台线程,在任务执行期间不会阻塞主线程。这样可以提高应用程序的响应性能,特别是在处理I/O密集型操作时,如网络请求或数据库查询。
链式调用和组合操作:支持链式调用,可以将多个异步任务按照顺序连接起来形成一个任务流水线。每个任务的执行依赖于前一个任务的结果,这种串行的处理方式可以简化异步任务的编写和管理。
异常处理:提供了异常处理的机制,可以通过异常回调方法来捕获和处理任务执行过程中的异常情况。这样可以更好地控制和处理任务执行过程中的异常,提供更健壮的代码。
转换和合并结果:提供了一系列的转换和合并操作,可以对任务的结果进行映射、转换和合并。这样可以方便地对任务的结果进行处理和转换,得到最终期望的结果。
多任务并行执行:支持等待多个任务并行执行,并等待它们全部完成或任意一个完成。这种能力使得在处理并发任务时可以更好地利用系统资源,提高任务执行的效率。
串行和并行的效率对比
测试批量上传了1000张图片,每张图片在579KB,一共564MB。使用串行方式上传,总时长为501秒,使用并行方式上传,总时长是108秒,通过对比优化前后的代码,可以明显看出使用CompletableFuture并发处理方式的效率更高。由于任务是并行执行的,多核处理器的能力得到了充分的利用,从而大大提高了批量上传的速度。
串行处理方式
/**
* describe: 批量上传图片
*
* @param files 图片文件集合
* @param fileId 文件夹id
* @param scheduleKey 上传进度key
* @date 2023年06月28日 11:42:03
* @author Tang
*/
@Override
public BatchUploadVO batchUpload2(MultipartFile[] files, Long fileId, String scheduleKey) {
//取上传配置
String jsonStr = CacheConfigure.getValue(CacheKeyConstant.IMG_RESOURCE_UPLOAD_CONFIG, String.class);
ImgResourceUploadConfigDTO config = JSONObject.toJavaObject(JSONObject.parseObject(jsonStr), ImgResourceUploadConfigDTO.class);
List<String> imgTypeList = Arrays.asList(config.getImgType().split(",")); List<String> errorNames = Lists.newCopyOnWriteArrayList();
String userName = SecurityAuthorHolder.getSecurityUser().getUsername(); for(MultipartFile file : files){
try {
RedisUtil.setInteger(CacheKeyConstant.UPLOAD_SCHEDULE_TOTAL + scheduleKey, files.length, CacheTimeConstant.BATCH_UPLOAD_EXPIRED_TIME);
String suffix = Objects.requireNonNull(file.getOriginalFilename()).substring(file.getOriginalFilename().lastIndexOf(".") + 1);
ServerException.Assert(!imgTypeList.contains(suffix), "文件格式不正确,支持" + String.join(",", imgTypeList));
ServerException.Assert(file.getSize() > config.getMaxSize() * 1024, "文件最大不能超过" + config.getMaxSize() + "K");
//上传
ImgResourceEntity saveData = upload(file, config);
saveData.setFileId(fileId);
saveData.setCreator(userName);
baseMapper.insert(saveData);
//缓存自增 供轮询查询实时进度
RedisUtil.incrementValue(CacheKeyConstant.UPLOAD_SCHEDULE_SUCCESS + scheduleKey, CacheTimeConstant.BATCH_UPLOAD_EXPIRED_TIME);
} catch (Exception e) {
errorNames.add(file.getOriginalFilename());
RedisUtil.incrementValue(CacheKeyConstant.UPLOAD_SCHEDULE_ERROR + scheduleKey, CacheTimeConstant.BATCH_UPLOAD_EXPIRED_TIME);
}
} BatchUploadVO vo = schedule(scheduleKey);
vo.setErrFileNames(errorNames);
return vo;
}
串行处理调用时间

并行处理方式
/**
* describe: 批量上传图片
*
* @param files 图片文件集合
* @param fileId 文件夹id
* @param scheduleKey 上传进度key
* @date 2023年06月28日 11:42:03
* @author Tang
*/
@Override
public BatchUploadVO batchUpload(MultipartFile[] files, Long fileId, String scheduleKey) {
ExecutorService executor = Executors.newFixedThreadPool(10); //取上传配置
String jsonStr = CacheConfigure.getValue(CacheKeyConstant.IMG_RESOURCE_UPLOAD_CONFIG, String.class);
ImgResourceUploadConfigDTO config = JSONObject.toJavaObject(JSONObject.parseObject(jsonStr), ImgResourceUploadConfigDTO.class);
List<String> imgTypeList = Arrays.asList(config.getImgType().split(",")); List<String> errorNames = Lists.newCopyOnWriteArrayList();
String userName = SecurityAuthorHolder.getSecurityUser().getUsername(); CompletableFuture<Void> allFutures = CompletableFuture.allOf(
Arrays.stream(files).map(v ->
CompletableFuture.runAsync(
() -> {
try {
RedisUtil.setInteger(CacheKeyConstant.UPLOAD_SCHEDULE_TOTAL + scheduleKey, files.length, CacheTimeConstant.BATCH_UPLOAD_EXPIRED_TIME);
String suffix = Objects.requireNonNull(v.getOriginalFilename()).substring(v.getOriginalFilename().lastIndexOf(".") + 1);
ServerException.Assert(!imgTypeList.contains(suffix), "文件格式不正确,支持" + String.join(",", imgTypeList));
ServerException.Assert(v.getSize() > config.getMaxSize() * 1024, "文件最大不能超过" + config.getMaxSize() + "K");
//上传
ImgResourceEntity saveData = upload(v, config);
saveData.setFileId(fileId);
saveData.setCreator(userName);
baseMapper.insert(saveData);
//缓存自增 供轮询查询实时进度
RedisUtil.incrementValue(CacheKeyConstant.UPLOAD_SCHEDULE_SUCCESS + scheduleKey, CacheTimeConstant.BATCH_UPLOAD_EXPIRED_TIME);
} catch (Exception e) {
errorNames.add(v.getOriginalFilename());
RedisUtil.incrementValue(CacheKeyConstant.UPLOAD_SCHEDULE_ERROR + scheduleKey, CacheTimeConstant.BATCH_UPLOAD_EXPIRED_TIME);
}
}, executor)
).toArray(CompletableFuture[]::new)
);
// 等待所有 CompletableFuture 完成
allFutures.join(); // 关闭线程池
executor.shutdown(); BatchUploadVO vo = schedule(scheduleKey);
vo.setErrImgFileNames(errorNames);
return vo;
}
并行调用处理时间

实现过程中的注意事项
线程池的使用:为了实现并发处理,可以使用线程池来管理并执行异步任务。通过合理设置线程池的大小和参数,可以控制并发线程的数量和资源的利用率。
异常处理:在并发处理中,每个任务都是独立执行的,因此需要适当处理任务中可能出现的异常情况,避免异常的影响扩散。
进度更新:为了实时更新上传进度,可以将每个任务的进度信息保存到Redis中,并在前端通过轮询查询的方式获取最新的进度信息。
线程安全:确保上传逻辑的线程安全性,避免多线程环境下的竞态条件和数据一致性问题。
总结
使用CompletableFuture来优化批量上传任务是一种高效且灵活的方式。通过并发处理,我们可以充分利用多核处理器的能力,提高任务的执行效率。同时,通过实时更新上传进度并返回总体的上传结果,可以给用户更好的体验。
在实现过程中,我们需要合理使用线程池、处理异常、保证数据同步和线程安全,以确保上传任务的稳定性和性能。同时,我们还可以利用CompletableFuture提供的方法来处理任务的结果、异常和其他相关操作,以满足具体的业务需求。
通过使用CompletableFuture进行批量上传任务的优化,可以显著提高系统的性能和用户体验,适用于需要处理大量并发任务的场景。
CompletableFuture之批量上传的更多相关文章
- 带进度条的文件批量上传插件uploadify
有时项目中需要一个文件批量上传功能时,个人认为uploadify是快速简便的解决方案. 先上效果图: 一. 下载uploadify 从官网下载uploadify的Flash版本(Flash版本免费,另 ...
- Discuz模拟批量上传附件发帖
简介 对于很多用discuz做资源下载站来说,一个个上传附件,发帖是很繁琐的过程.如果需要批量上传附件发帖,就需要去模拟discuz 上传附件的流程. 模拟上传 discuz 附件逻辑 dz附件储存在 ...
- Mvc利用淘宝Kissy uploader实现图片批量上传附带瀑布流的照片墙
前言 KISSY 是由阿里集团前端工程师们发起创建的一个开源 JS 框架.它具备模块化.高扩展性.组件齐全,接口一致.自主开发.适合多种应用场景等特性.本人在一次项目中层使用这个uploader组件. ...
- 利用uploadify+asp.net 实现大文件批量上传。
前言 现在网上文件上传组件随便一搜都是一大堆,不过看大家一般都在用uploadify这个来上传文件.由于项目需要,我在来试了一下.因为第一次使用,也遇到了很多问题,特此记录! ------------ ...
- uploadify文件批量上传
uploadify能够时间文件的批量上传,JS文件包下载地址,使用说明可以参考官网文档(http://www.uploadify.com/documentation/) 使用方法如下代码: $(&qu ...
- 文件上传和下载(可批量上传)——Spring(三)
在文件上传和下载(可批量上传)——Spring(二)的基础上,发现了文件下载时,只有在Chrome浏览器下文件名正常显示,还有发布到服务器后,不能上传到指定的文件夹目录,如上传20160310.txt ...
- ux.plup.File plupload 集成 ux.plup.FileLis 批量上传预览
//plupload 集成 Ext.define('ux.plup.File', { extend: 'Ext.form.field.Text', xtype: 'plupFile', alias: ...
- WEB版一次选择多个文件进行批量上传(Plupload)的解决方案
WEB版一次选择多个文件进行批量上传(Plupload)的解决方案 转载自http://www.cnblogs.com/chillsrc/archive/2013/01/30/2883648.htm ...
- 文件批量上传的工具,要实现暂停继续、断点续传等功能(使用QtNetwork和QHttpMultiPart,和定时器检查超时)
最近在做一个文件批量上传的工具,要实现暂停继续.断点续传等功能.利用Qt自带的QtNetwork模块,完成这些需求并没有费多少周章,主要思路就是将文件分块,然后用while循环依次传输.具体实现代码比 ...
- KindEditor图片批量上传
KindEditor编辑器图片批量上传采用了上传插件swfupload.swf,所以后台上传文件方法返回格式应为JSONObject的String格式(注). JSONObject格式: JSONOb ...
随机推荐
- JS引擎(2):Java平台上JavaScript引擎—Rhino/Nashorn概述
可以后端开发的 javascript引擎有 Chrome V8 基于C++ java的Rhino引擎(JDK6被植入),Java8 被替换为Nashorn Rhino和Nashorn都是用Java实现 ...
- ZR.Admin小改和VUE3版本体验
前言 孔乙己显出极高兴的样子,将两个指头的长指甲敲着柜台,点头说:"对呀,对呀!......回字有四样写法,你知道么?" 大家好,我是44岁的大龄程序员码农阿峰.阿峰从事编程二十年 ...
- 深度剖析Redis九种数据结构实现原理,建议收藏
1. Redis介绍 Redis 是一个高性能的键值存储系统,支持多种数据结构. 包含五种基本类型 String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),和 ...
- Python程序笔记20230302
Alice.Bob 和他们的朋友们 问题主体 密码学家 Rivest.Shamir.Adleman 于1977年4月撰写了一篇论文<数字签名与公钥密码学>(On Digital Signa ...
- 微信小程序隐藏页面滚动条
开发小程序时,经常会碰到页面长度超过屏幕高度,然后下拉时会出现滚动条,对于一些有强迫症的人来说是不可忍受的. 网上看了好多,写的.都评论有起作用或者不起作用的. 我在这分享一个全局隐藏滚动条的方式. ...
- 程序猿要chatpgpt干掉了?
如何拥抱被chatpgpt拉开的人工智能大时代 昨天 chatgpt-4 发布了.我看到好多技术圈的人都惶恐着,以后咱们都要失业了/(ㄒoㄒ)/~~ 和之前差不多的是毫无意外地又引动了一大波舆论.虽然 ...
- maven下载和配置信息
1. 下载maven: https://maven.apache.org/ 2. 进入官网点击 Download 3. 最新版直接下载 .tar.gz 格式linux系统 .zip windows系统 ...
- [C++提高编程] 3.4 案例-评委打分
文章目录 3.4 案例-评委打分 3.4.1 案例描述 3.4.2 实现步骤 3.4 案例-评委打分 3.4.1 案例描述 有5名选手:选手ABCDE,10个评委分别对每一名选手打分,去除最高分,去除 ...
- 大话AI绘画技术原理与算法优化
引子 博主很长一段时间都没有发文,确实是在忙一些技术研究. 如标题所示,本篇博文主要把近段时间的研究工作做一个review. 看过各种相关技术的公关文章,林林总总,水分很多. 也确实没有多少人能把一些 ...
- Spring Boot 整合邮件服务
参考教程 首先参考了 Spring Boot整合邮件配置,这篇文章写的很好,按照上面的操作一步步走下去就行了. 遇到的问题 版本配置 然后因为反复配置版本很麻烦,所以参考了 如何统一引入 Spring ...