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

记一次java for循环改造多线程的操作

背景

今天在开发质量平台时需要获取某些数据,要请求公司某个工程的OpenAPI接口A。此接口为返回通用数据的接口,且接口本身的RT都在2~3秒之间。使用该接口,需要进行两次循环获取,然后对返回数据进行处理组装,才能得到我这边工程需要的数据。

在最开始的时候,我天真的写了两层循环,外层循环为一星期的每一天,内层循环为选取的几个版本号。结果发现整个请求过程(请求接口B和C获取版本相关数据->两层循环请求接口A->数据过滤筛选->数据组装排序)下来,响应时间来到了恐怖的2分钟(🤔要被领导骂死了)

同时数据又都要实时获取,无法使用定时任务和缓存的方式

解决思路

将for循环改为多线程的方式进行执行,一种常用的方法是使用Executor框架

package com.xxx.xxx;...
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Main {public static void main(String[] args) {// 模拟数据库中的100条数据;List list = new ArrayList();for (int i = 0; i < 100; i++) {list.add(i);}//Executors创建线程池new固定的10个线程ExecutorService taskExecutor = Executors.newCachedThreadPool();final CountDownLatch latch = new CountDownLatch(list.size());//用于判断所有的线程是否结束System.out.println("个数==" + list.size());for (int m = 0; m < list.size(); m++) {final int n = m;//内部类里m不能直接用,所以赋值给nRunnable run = new Runnable() {public void run() {try {System.out.println("我在执行=" + n);} finally {latch.countDown(); //每次调用CountDown(),计数减1}}};taskExecutor.execute(run);//开启线程执行池中的任务。还有一个方法submit也可以做到,它的功能是提交指定的任务去执行并且返回Future对象,即执行的结果}try {//等待所有线程执行完毕latch.await();//主程序执行到await()函数会阻塞等待线程的执行,直到计数为0} catch (InterruptedException e) {e.printStackTrace();}taskExecutor.shutdown();//关闭线程池//所有线程执行完毕,执行主线程}}

注意:在使用多线程时,需要注意线程安全问题,如果程序中使用了共享变量,需要进行同步处理。

业务使用

@Override
public List<JSONObject> getBoomCrash(String appId, String androidEventType, String OS, Set<String> appVersionSet, List<Map<String, Long>> timeScope) throws URISyntaxException, IOException {Map<String, String[]> versionTagMap = new HashMap<>();// 首先获取版本信息。业务代码,省略....// 第一步先获取传入版本所有的crash数据,并过滤掉版本首次出现的。业务代码,省略List<BoomCrashDataVo> boomCrashDataList = ...// 第二步,获取所有版本和UV【以昨日数据为标准,结果是UV倒序排列】。业务代码,省略List<CrashVersionUvDataVo> versionUvResult = ...// 第三步,判断当前版本的上一个全量版本。业务代码,省略String lastVersion = ...List versionList = new ArrayList();for (String key : appVersionSet) {versionList.add(key);}versionList.add(lastVersion);String versionListstr = StringUtils.join(versionList, ",");List<JSONObject> boomCrashDataListNew = new ArrayList<>();// 第四步,循环判断获取某个issue数据的数量情况// Executors创建线程池new固定的10个线程ExecutorService taskExecutor = Executors.newCachedThreadPool();final CountDownLatch latch = new CountDownLatch(boomCrashDataList.size());//用于判断所有的线程是否结束for (BoomCrashDataVo boomCrashData : boomCrashDataList) {Runnable run = new Runnable() {public void run() {try {// 这里是业务代码...} finally {latch.countDown(); //每次调用CountDown(),计数减1}}};taskExecutor.execute(run);}try {//等待所有线程执行完毕latch.await(); //主程序执行到await()函数会阻塞等待线程的执行,直到计数为0} catch (InterruptedException e) {e.printStackTrace();}taskExecutor.shutdown();// 按照TOP进行正序排序Collections.sort(boomCrashDataListNew, new Comparator<JSONObject>() {@Overridepublic int compare(JSONObject v1, JSONObject v2) {Integer uv1 = v1.getIntValue("topNumber");Integer uv2 = v2.getIntValue("topNumber");return uv1.compareTo(uv2);}});return boomCrashDataListNew;
}

改造成果

响应时间降到了20~30秒,和业务沟通在可接受范围内。同时,前端我修改成了在请求数据过程中显示加载组件(参考antd的),这样就不会显示太过突兀,提升用户使用体验。

深入学习

执行器服务

java.util.concurrent.ExecutorService 接口表示一个异步执行机制,使我们能够在后台执行任务。因此一个 ExecutorService 很类似于一个线程池。实际上,存在于 java.util.concurrent 包里的 ExecutorService 实现就是一个线程池实现。

ExecutorService executorService = Executors.newFixedThreadPool(10);executorService.execute(new Runnable() {public void run() {System.out.println("Asynchronous task");}});
executorService.shutdown();

首先使用 newFixedThreadPool() 工厂方法创建一个 ExecutorService。这里创建了一个十个线程执行任务的线程池。然后,将一个 Runnable 接口的匿名实现类传递给 execute() 方法。这将导致 ExecutorService 中的某个线程执行该 Runnable。

任务委派

下图说明了一个线程是如何将一个任务委托给一个 ExecutorService 去异步执行的:

image.png

一旦该线程将任务委派给 ExecutorService,该线程将继续它自己的执行,独立于该任务的执行。

ExecutorService实现.

既然 ExecutorService 是个接口,如果你想用它的话就得去使用它的实现类之一。 java.util.concurrent 包提供了 ExecutorService 接口的以下实现类:

ThreadPoolExecutor
ScheduledThreadPoolExecutor

ExecutorService使用

有几种不同的方式来将任务委托给 ExecutorService 去执行:

  1. execute(Runnable)
  2. submit(Runnable)
  3. submit(Callable)
  4. invokeAny(…)
  5. invokeAll(…)

execute(Runnable)

execute(Runnable) 方法要求一个 java.lang.Runnable 对象,然后对它进行异步执行。以下是使用 ExecutorService 执行一个 Runnable 的示例:

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {public void run() {System.out.println("Asynchronous task");} 
});
executorService.shutdown();

没有办法得知被执行的 Runnable 的执行结果。如果有需要的话你得使用一个 Callable(以下将做介绍)。

submit(Runnable)

submit(Runnable) 方法也要求一个 Runnable 实现类,但它返回一个 Future 对象。这个Future 对象可以用来检查 Runnable 是否已经执行完毕。
以下是 ExecutorService submit() 示例:

Future future = executorService.submit(new Runnable() {public void run() {System.out.println("Asynchronous task");}
});
future.get(); //returns null if the task has finished correctly

submit(Callable)

submit(Callable) 方法类似于 submit(Runnable) 方法,除了它所要求的参数类型之外。
Callable 实例除了它的 call() 方法能够返回一个结果之外和一个 Runnable 很相像。

Runnable.run() 不能够返回一个结果。Callable 的结果可以通过 submit(Callable) 方法返回的 Future 对象进行获取。以下是一个

ExecutorService Callable 示例:

Future future = executorService.submit(new Callable(){public Object call() throws Exception {System.out.println("Asynchronous Callable");return "Callable Result";}
});
System.out.println("future.get() = " + future.get());// 输出
Asynchronous Callable  
future.get() = Callable Result

invokeAny()

invokeAny() 方法要求一系列的 Callable 或者其子接口的实例对象。调用这个方法并不会返回一个 Future,但它返回其中一个 Callable 对象的结果。无法保证返回的是哪个 Callable 的结果 - 只能表明其中一个已执行结束。

如果其中一个任务执行结束(或者抛了一个异常),其他 Callable 将被取消。

以下是示例代码:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {public String call() throws Exception {return "Task 1";}
});
callables.add(new Callable<String>() {public String call() throws Exception {return "Task 2";}
});
callables.add(new Callable<String>() {public String call() throws Exception {return "Task 3";}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown()

上述代码将会打印出给定 Callable 集合中的一个的执行结果

invokeAll()

invokeAll() 方法将调用你在集合中传给 ExecutorService 的所有 Callable 对象。invokeAll() 返回一系列的 Future 对象,通过它们你可以获取每个 Callable 的执行结果。

记住,一个任务可能会由于一个异常而结束,因此它可能没有 “成功”。无法通过一个 Future 对象来告知我们是两种结束中的哪一种。

以下是一个代码示例:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {public String call() throws Exception {return "Task 1";}
});
callables.add(new Callable<String>() {public String call() throws Exception {return "Task 2";}
});
callables.add(new Callable<String>() {public String call() throws Exception {return "Task 3";}
});
List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future : futures){System.out.println("future.get = " + future.get());
}
executorService.shutdown();

ExecutorService关闭

使用完 ExecutorService 之后你应该将其关闭,以使其中的线程不再运行。比如,如果你的应用是通过一个 main() 方法启动的,之后 main 方法退出了你的应用,如果你的应用有一个活动的 ExexutorService 它将还会保持运行。ExecutorService 里的活动线程阻止了 JVM 的关闭。

要终止 ExecutorService 里的线程你需要调用 ExecutorService 的 shutdown() 方法。

ExecutorService 并不会立即关闭,但它将不再接受新的任务,而且一旦所有线程都完成了当前任务的时候,ExecutorService 将会关闭。在 shutdown() 被调用之前所有提交给ExecutorService 的任务都被执行。

如果你想要立即关闭 ExecutorService,你可以调用 shutdownNow() 方法。这样会立即尝试停止所有执行中的任务,并忽略掉那些已提交但尚未开始处理的任务。无法担保执行任务的正确执行。可能它们被停止了,也可能已经执行结束。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你! 

http://www.lryc.cn/news/262876.html

相关文章:

  • Java面试整理-Java复制
  • wsl kafka的简单应用
  • 2023年国赛高教杯数学建模D题圈养湖羊的空间利用率解题全过程文档及程序
  • Flink系列之:Table API Connectors之Raw Format
  • 社交网络分析3:社交网络隐私攻击、保护的基本概念和方法 + 去匿名化技术 + 推理攻击技术 + k-匿名 + 基于聚类的隐私保护算法
  • 2023大湾区汽车创新大会在深圳坪山开幕
  • Graylog 中日志级别及其对应的数字
  • 智能手表上的音频(五):录音
  • 2023.12.17 关于 Redis 的特性和应用场景
  • 智能优化算法应用:基于社会群体算法3D无线传感器网络(WSN)覆盖优化 - 附代码
  • Kotlin 笔记 -- Kotlin 语言特性的理解(二)
  • 数据结构【1】:数组专题
  • 【Spring】Spring 事务
  • Ubuntu 虚拟机环境,编译AOSP源码
  • 2023.12.18杂记
  • 智能优化算法应用:基于阿基米德优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码
  • K8s内容器拓扑图工具
  • 掌握 Babel:让你的 JavaScript 与时俱进(上)
  • Mysql进阶-InnoDB引擎事务原理及MVCC
  • 「X」Embedding in NLP|神经网络和语言模型 Embedding 向量入门
  • JVM-11-运行时栈帧结构
  • 【经典LeetCode算法题目专栏分类】【第6期】二分查找系列:x的平方根、有效完全平方数、搜索二位矩阵、寻找旋转排序数组最小值
  • 【大麦小米学量化】使用xtquant调用迅投MiniQMT客户端定时操作逆回购,再也不担心忘了赚零花钱了(含完整源代码)
  • php hyperf 读取redis,存储到数据库
  • 云原生之深入解析K8S 1.27新特性如何简化状态服务跨集群平滑迁移
  • 鸿蒙OS:打破界限的操作系统新星
  • 预测性维护在汽车制造行业中的应用
  • 分布式链路追踪 —— 基于Dubbo的traceId追踪传递
  • 【uniapp小程序-上拉加载】
  • ubuntu添加路由