当前位置: 首页 > news >正文

JAVA常用的异步处理方法总结

前言

在java项目开发过程中经常会遇到比较耗时的任务,通常是将这些任务做成异步操作,在java中实现异步操作有很多方法,本文主要总结一些常用的处理方法。为了简化,我们就拿一个实际的案例,再用每种方法去实现,对比看看这些方法有什么优缺点。

具体案例:
在C://img/url.txt中有1000个图片URL,我们需要将这些图片下载到C://img/download目录下。
需要将每张图片耗时累加起来,输出最后的时间

img.txt数据格式

https://a.com/1.jpg
https://a.com/2.jpg
https://a.com/3.jpg
...

计算Demo

比如 下载1.jpg 耗时1ms ,下载 2.jpg耗时2ms,…下载 n.jpg耗时 n ms
最终我们总耗时 1+2+3+…1000=500500ms

公共方法

为了方便测试我们先定义一个DownloadImg下载接口

public interface DownloadImg {/**** @param urls 需要下载图片的url* @return  所有图片下载总耗时ms*/Long download(List<String> urls) throws Exception ;/**** @param url 下载单个图片的URL* @return 下载单张图片耗时 ms*/Long download(String url) throws Exception;/*** 关闭线程池*/void  shutdown() throws InterruptedException;/***  提交任务* @param task* @return*/Future<?> submit(Runnable task);
}

为了简化下载、创建线程池、时间累加等操作,我们抽象一些共用方法:

public abstract class AbstractDownloadImg implements DownloadImg {//总耗时public  AtomicLong sumTime=new AtomicLong(0L);public ExecutorService executorService = Executors.newFixedThreadPool(10);private AtomicInteger i = new AtomicInteger(1);public Long download(String url) {try {Long startTime = System.currentTimeMillis();FileUtils.copyURLToFile(new URL(url), new File(String.format("C:\img\download\%s.jpg", i.getAndIncrement())));Long castTime = System.currentTimeMillis() - startTime;System.out.println(Thread.currentThread().getName() + " download :" + url + " success, cast :" + castTime + " ms");return castTime;} catch (Exception e) {e.printStackTrace();return 0L;}}public void shutdown() throws InterruptedException {Thread.sleep(1000L);executorService.shutdown();}public Future<?> submit(Runnable task) {return executorService.submit(task);}
}

具体实现

一. 使用Future任务多线程下载

这种方法很是自然而然能想到,文件中有1000个图片,单个线程依次去下载太慢了,于是我们可以把1000张图片分成10个子任务,每个子任务去下载100张图片,子任务中把这100张图片耗时加起来,然后再把这10个子任务的耗时相加就是总时长了。

public class MultiThreadDownload extends AbstractDownloadImg implements DownloadImg {@Overridepublic Long download(List<String> imgUrls) throws Exception {//每100个一组List<List<String>> urls = Lists.partition(imgUrls, 100);//每个线程下载100张图片耗时返回结果List<Future<Long>> futures = new ArrayList<>();//分成10个线程,每个线程下载100个urls.forEach(subUrls->{FutureTask<Long> future= new FutureTask<>(() -> subUrls.stream().map(this::download).mapToLong(x->x).sum());//反回结果添加到futures中futures.add(future);//提交到线程池中submit(future);});//每线程耗时时间累加for(Future<Long> f:futures){sumTime.getAndAdd(f.get());}return sumTime.get();}
}

优点 :比较简单,大部人第一眼能想到的方法

缺点 :会产生水桶效应。如果前9个线程下载的都是小图片,很快下载完成了,第10个线程全是大图片,当最后9个线都空着时,第10个线程任务可能还在等待

二. CompletableFuture

上面的方法,是我们自己写Future,然后拿到返回值相加,在JUC包下面有个CompletableFuture,我们可以直接拿来用。

public class CompletableFutureDownload extends AbstractDownloadImg implements DownloadImg {@Overridepublic Long download(List<String> imgUrls) {//创建10个CompletableFutureCompletableFuture<Long>[] completableFutures = new CompletableFuture[imgUrls.size()];for (int i = 0; i < imgUrls.size(); i++) {String url = imgUrls.get(i);completableFutures[i] = CompletableFuture.supplyAsync(() -> download(url), executorService).whenComplete((k, v) -> sumTime.getAndAdd(k));}//所有任务合成一个CompletableFutureCompletableFuture allFuture = CompletableFuture.allOf(completableFutures).whenComplete((k, v) -> {System.out.println("all future complete cast: {} " + sumTime.get() + "ms");});//等待所有任务完成allFuture.join();return sumTime.get();}
}

三. 使用CountDownLatch

使用Futrue获取线程池返回结果还是有点麻烦的,在JUC包中有个CountDownLatch(倒计数门闩),使用这个实现代码就简化很多了,我们只需要把每张图片下载耗时累加起来,最后等待所有任务完成就OK了。

public class CountDownLatchDownload extends AbstractDownloadImg implements DownloadImg {@Overridepublic Long download(List<String> imgUrls) throws Exception {//门栓计数次CountDownLatch countDownLatch = new CountDownLatch(imgUrls.size());//总耗时AtomicLong sumTime = new AtomicLong(0L);for (String url : imgUrls) {submit(() -> {try {sumTime.getAndAdd(download(url));} finally {countDownLatch.countDown();}});}//等待所有任务结束countDownLatch.await();return sumTime.get();}
}

四. 使用lambda中的parallelStream

有了上面倒计数门闩,那我们自然可以想到JAVA8 lambda中的parallelStream了,用了parallelStream上面的代码又可以简化了。

public class ParallelStreamDownload extends AbstractDownloadImg implements DownloadImg {@Overridepublic Long download(List<String> imgUrls) throws Exception {return imgUrls.parallelStream().mapToLong(this::download).sum();}
}

需要注意的地parallelStream底层实现是使用的fork join,默认线程数是CPU核数,而且是全局共用一个线程池的,这点很重要,如果不指定线程池,项目别处使用了parallelStream,可能影响你你当前这处代码的执行速度。当然我们可以设置默认线程数和指定线程池。

//设置parallelStream线程数量为20个
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");

指定ForkJoinPool的parallelStream

public class ParallelStreamDownload extends AbstractDownloadImg implements DownloadImg {@Overridepublic Long download(List<String> imgUrls) throws Exception {ForkJoinPool forkJoinPool = new ForkJoinPool(20);return forkJoinPool.submit(() -> imgUrls.parallelStream().mapToLong(this::download).sum()).get();}
}

优点 :代码非常简洁

缺点 :隐藏了很多细节,使用不当可能导致不可预估的后果,如果不了解内部原理,你都不知道为什么你的代码卡住了

五. 使用Fork/Join

parallelStream底层就是用的Fork/Join来实现的,所以我们也可以自己用Fork/Join来实现。

public class ForkJoinDownload extends AbstractDownloadImg implements DownloadImg {static class DownloadJoinTask extends RecursiveTask<Long> {//需要下载的URLprivate List<String> urls;//子任务最多条数private Integer MAX_TASK_COUNT = 100;private DownloadImg downloadImg;public DownloadJoinTask(List<String> urls, DownloadImg downloadImg) {this.urls = urls;this.downloadImg = downloadImg;}@Overrideprotected Long compute() {//当前任务<=100个,执行下载操作if (urls.size() <= MAX_TASK_COUNT) {return urls.stream().map(x -> {try {return downloadImg.download(x);} catch (Exception e) {e.printStackTrace();return 0L;}}).mapToLong(x -> x).sum();} else {//当前前任务拆分成两个任务ForkJoinDownload.DownloadJoinTask leftTask = new ForkJoinDownload.DownloadJoinTask(urls.subList(0, urls.size() / 2), downloadImg);ForkJoinDownload.DownloadJoinTask rightTask = new ForkJoinDownload.DownloadJoinTask(urls.subList(urls.size() / 2, urls.size()), downloadImg);//提交子任务invokeAll(leftTask, rightTask);return leftTask.join() + rightTask.join();}}}@Overridepublic Long download(List<String> imgUrls) throws Exception {ForkJoinPool forkJoinPool = new ForkJoinPool(20);DownloadJoinTask downloadJoinTask = new DownloadJoinTask(imgUrls, this);ForkJoinTask<Long> taskFuture = forkJoinPool.submit(downloadJoinTask);sumTime.addAndGet(taskFuture.get());return sumTime.get();}
}

优点 :工作窃取算法,不会产生水桶效应

缺点 :需要正确理解Fork/Join模型的任务执行逻辑才能写出好代码,有一定的门槛

测试代码

public static void main(String[] args) throws Exception {List<String> urls = FileUtils.readLines(new File("C:\img\url.txt"), Charset.defaultCharset());//DownloadImg downloadImg=new MultiThreadDownload();//DownloadImg downloadImg=new CountDownLatchDownload();//DownloadImg downloadImg=new ParallelStreamDownload();//DownloadImg downloadImg=new ForkJoinDownload();DownloadImg downloadImg = new CompletableFutureDownload();System.out.println("download all url cast: " + downloadImg.download(urls) + " ms");downloadImg.shutdown();
}

总结

本文主要通过下载图片这个具体的案例,介绍JAVA中5种常用的方法如何异步处理比较耗时的任务,并对比了优缺点,希望在项目过遇到类似的需求,可以帮助你找到合适的方法。

相关文章:

JAVA常用的异步处理方法总结

前言 在java项目开发过程中经常会遇到比较耗时的任务&#xff0c;通常是将这些任务做成异步操作&#xff0c;在java中实现异步操作有很多方法&#xff0c;本文主要总结一些常用的处理方法。为了简化&#xff0c;我们就拿一个实际的案例&#xff0c;再用每种方法去实现&#xf…...

GitLab统计代码量

gitlab官方文档&#xff1a;https://docs.gitlab.com/ee/api/index.html 1、生成密钥 登录gitlab&#xff0c;编辑个人资料&#xff0c;设置访问令牌 2、获取当前用户所有可见的项目 接口地址 GET请求 http://gitlab访问地址/api/v4/projects?private_tokenxxx 返回参数 …...

Linux TCP MIB统计汇总

概述 在 linux > 4.7 才将所有TCP丢包收敛到 函数 tcp_drop 中 指标详解 cat /proc/net/netstat 格式化命令 cat /proc/net/netstat | awk (f0) {name$1; i2; while ( i<NF) {n[i] $i; i }; f1; next} (f1){ i2; while ( i<NF){ printf "%s%s %d\n", …...

记录 docker linux部署jar

第一步 web sso user admin 中yml文件还原到阿里mysql数据库 第二步 各个jar进行打包处理 第三步 正式服务器的Jar备份 第四步 拉取以上jar包 到正式服务器中 第五步 查看 docker images 其中 web_service 1.0.2是上一个版本 上一个版本build 镜像命令是这样的&#xff08;需…...

【Linux】教你用进程替换制作一个简单的Shell解释器

本章的代码可以访问这里获取。 由于程序代码是一体的&#xff0c;本章在分开讲解各部分的实现时&#xff0c;代码可能有些跳跃&#xff0c;建议在讲解各部分实现后看一下源代码方便理解程序。 制作一个简单的Shell解释器 一、观察Shell的运行状态二、简单的Shell解释器制作原理…...

onMeasure里如何重置只有1个子view一行满屏, 若有多个自适应一行

onMeasure里如何重置只有1个子view一行满屏, 若有多个自适应一行 可以尝试在 onMeasure 方法中重写 measureChildWithMargins 或 measureChild 方法来实现这个需求。 对于只有一个字的 View,我们可以把它的宽度设为屏幕宽度,高度设为最大高度,这样这个 View 就会占满一整行…...

Postman创建项目 对接口发起请求处理

查看本文之前 您需要理解了解 Postman 的几个简单工作区 如果还没有掌握 可以先查看我的文章 简单认识 Postman界面操作 那么 掌握之后 我们就可以正式来开启我们的接口测试 我们先选择 Collections 我们点上面这个加号 多拉一个项目出来 然后 我们选我们刚加号点出来的项目…...

在Vue3项目中js-cookie库的使用

文章目录 前言1.安装js-cookie库2.引入、使用js-cookie库 前言 今天分享一下在Vue3项目中引入使用js-cookie。 1.安装js-cookie库 js-cookie官网 安装js-cookie&#xff0c;输入 npm i js-cookie安装完成可以在package.json中看到&#xff1a; 安装以后&#xff0c;就可…...

【论文笔记】Attention和Visual Transformer

Attention和Visual Transformer Attention和Transformer为什么需要AttentionAttention机制Multi-head AttentionSelf Multi-head Attention&#xff0c;SMA TransformerVisual Transformer&#xff0c;ViT Attention和Transformer Attention机制在相当早的时间就已经被提出了&…...

独立IP服务器和共享IP服务器有什么区别

在选择一个合适的服务器时&#xff0c;最常见的选择是共享IP服务器和独立IP服务器。尽管两者看起来很相似&#xff0c;但它们有着很大的不同。本文将详细介绍共享IP服务器和独立IP服务器的不同之处&#xff0c;以及如何选择适合您需求的服务器。 一、什么是共享IP服务器? 共享…...

Java8

Java8 &#xff08;一&#xff09;、双列集合&#xff08;二&#xff09;、Map集合常用api&#xff08;三&#xff09;、Map集合的遍历方式&#xff08;四&#xff09;、HashMap&#xff08;五&#xff09;、LinkedHashMap&#xff08;六&#xff09;、TreeMap&#xff08;七&a…...

nn.conv1d的输入问题

Conv1d(in_channels, out_channels, kernel_size, stride1, padding0, dilation1, groups1, biasTrue) in_channels(int) – 输入信号的通道。在文本分类中&#xff0c;即为词向量的维度out_channels(int) – 卷积产生的通道。有多少个out_channels&#xff0c;就需要多少个1维…...

js判断是否为null,undefined,NaN,空串或者空对象

js判断是否为null&#xff0c;undefined&#xff0c;NaN&#xff0c;空串或者空对象 这里写目录标题 js判断是否为null&#xff0c;undefined&#xff0c;NaN&#xff0c;空串或者空对象特殊值nullundefinedNaN空字符串&#xff08;""&#xff09;空对象&#xff08;…...

Java每日一练(20230501)

目录 1. 路径交叉 &#x1f31f;&#x1f31f; 2. 环形链表 &#x1f31f;&#x1f31f; 3. 被围绕的区域 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏…...

从零开始学习Web自动化测试:如何使用Selenium和Python提高效率?

B站首推&#xff01;2023最详细自动化测试合集&#xff0c;小白皆可掌握&#xff0c;让测试变得简单、快捷、可靠https://www.bilibili.com/video/BV1ua4y1V7Db 目录 引言&#xff1a; 一、了解Web自动化测试的基本概念 二、选择Web自动化测试工具 三、学习Web自动化测试的…...

fastdfs环境搭建

安装包下载路径 libfastcommon下载地址&#xff1a;https://github.com/happyfish100/libfastcommon/releasesFastDFS下载地址&#xff1a;https://github.com/happyfish100/fastdfs/releasesfastdfs-nginx-module下载地址&#xff1a;https://github.com/happyfish100/fastdf…...

有什么牌子台灯性价比高?性价比最高的护眼台灯

由心感叹现在的孩子真不容易&#xff0c;学习压力比我们小时候大太多&#xff0c;特别是数学&#xff0c;不再是简单的计算&#xff0c;而更多的是培养学生其他思维方式&#xff0c;有时候我都觉得一年级数学题是不是超纲了。我女儿现在基本上都是晚上9点30左右上床睡觉&#x…...

信息系统项目管理师 第9章 项目范围管理

1.管理基础 1.产品范围和项目范围 产品范围:某项产品、服务或成果所具有的特征和功能。根据产品需求来衡量。 项目范围:包括产品范围&#xff0c;是为交付具有规定特性与功能的产品、服务或成果而必须完成的工作。项目管理计划来衡量 2.管理新实践 更加注重与商业分析师一起…...

【Android入门到项目实战-- 8.2】—— 使用HTTP协议访问网络

目录 一、使用HttpURLConnection 1、使用Android的HttpURLConnection步骤 1&#xff09;获取HttpURLConnection实例 2)设置HTTP请求使用的方法 3)定制HTTP请求&#xff0c;如连接超时、读取超时的毫秒数 4)调用getInputStream()方法获取返回的输入流 5)关闭HTTP连接 2、…...

Go官方指南(五)并发

Go 程 Go 程&#xff08;goroutine&#xff09;是由 Go 运行时管理的轻量级线程。 go f(x, y, z) 会启动一个新的 Go 程并执行 f(x, y, z) f, x, y 和 z 的求值发生在当前的 Go 程中&#xff0c;而 f 的执行发生在新的 Go 程中。 Go 程在相同的地址空间中运行&#xff0c…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

Typeerror: cannot read properties of undefined (reading ‘XXX‘)

最近需要在离线机器上运行软件&#xff0c;所以得把软件用docker打包起来&#xff0c;大部分功能都没问题&#xff0c;出了一个奇怪的事情。同样的代码&#xff0c;在本机上用vscode可以运行起来&#xff0c;但是打包之后在docker里出现了问题。使用的是dialog组件&#xff0c;…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用

一、方案背景​ 在现代生产与生活场景中&#xff0c;如工厂高危作业区、医院手术室、公共场景等&#xff0c;人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式&#xff0c;存在效率低、覆盖面不足、判断主观性强等问题&#xff0c;难以满足对人员打手机行为精…...

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

RushDB开源程序 是现代应用程序和 AI 的即时数据库。建立在 Neo4j 之上

一、软件介绍 文末提供程序和源码下载 RushDB 改变了您处理图形数据的方式 — 不需要 Schema&#xff0c;不需要复杂的查询&#xff0c;只需推送数据即可。 二、Key Features ✨ 主要特点 Instant Setup: Be productive in seconds, not days 即时设置 &#xff1a;在几秒钟…...

Linux入门(十五)安装java安装tomcat安装dotnet安装mysql

安装java yum install java-17-openjdk-devel查找安装地址 update-alternatives --config java设置环境变量 vi /etc/profile #在文档后面追加 JAVA_HOME"通过查找安装地址命令显示的路径" #注意一定要加$PATH不然路径就只剩下新加的路径了&#xff0c;系统很多命…...