记一次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不能直接用,所以赋值给n
            Runnable 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>() {
        @Override
        public 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() 方法。这样会立即尝试停止所有执行中的任务,并忽略掉那些已提交但尚未开始处理的任务。无法担保执行任务的正确执行。可能它们被停止了,也可能已经执行结束。

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【NI-RIO入门】扫描模式

于NI KB摘录 所有CompactRIO设备都可以访问CompactRIO扫描引擎和LabVIEW FPGA。 CompactRIO 904x 系列是第一个引入 DAQmx 功能的产品线。 扫描引擎&#xff08;IO 变量&#xff09; – 主要为迁移和初始开发而设计。控制循环频率高达 1 kHz1&#xff0c;性能控制器上的频率更…

kill编译异常处理

当kill编译时出现如下警告 Build target Target 1 linking... *** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESSSEGMENT: ?PR?_LCD_SHOWCHAR?LCD1602 *** WARNING L16: UNCALLED SEGMENT, IGNORED FOR OVERLAY PROCESSSEGMENT: ?PR?_LCD_SHOWSTRING?LCD…

代码随想录第三十五天(一刷C语言)|整数拆分不同的二叉搜索树

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、整数拆分 思路&#xff1a;参考carl文档。 1、确定dp数组以及下标的含义&#xff1a;分拆数字i&#xff0c;可以得到的最大乘积为dp[i]。 2、确定递推公式&#xff1a;从1遍历j&#…

Nginx location+Nginx rewrite(重写)(新版)

Nginx locationNginx rewrite(重写) Nginx locationNginx rewrite(重写)一、location1、常用的Nginx 正则表达式2、location的类型3、location 的匹配规则4、location 优先级5、location 示例说明5.1只修改网页路径5.2修改nginx配置文件和网页路径5.3一般前缀5.4正则匹配5.5前缀…

C# WPF上位机开发(usb设备访问)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 目前很多嵌入式设备都支持usb访问&#xff0c;特别是很多mcu都支持高速usb访问。和232、485下个比较&#xff0c;usb的访问速度和它们基本不在一个…

miRMaker

Introduction 除了miRNA表达数据&#xff0c;各种miRNA相关的知识也强有力地支持了对miRNA功能相互作用的理解。 那些具有许多共同调控靶基因或疾病的miRNAs可能具有相似的功能 一些方法通过考虑实验验证的miRNA-靶标关系来评估miRNA相互作用&#xff0c;评估miRNA功能相互作…

自清洗过滤器工作原理尺寸选型参数,内部结构,压差开关如何调节

​ 1&#xff1a;全自动自清洗过滤器设备介绍 全自动反冲洗过滤器是水净化过程中不可缺少的处理手段&#xff0c;用于拦截水中的各种杂质&#xff0c;以净化水质或保护系统中其他设备的正常工作。普通网式过滤器因其结构简单、过滤效果好、阻力小而广泛应用于水源过滤、工业循…

【Android Studio】各个版本下载地址

下载地址&#xff1a; https://developer.android.com/studio/archive?hlzh-cn

如何用 Cargo 管理 Rust 工程系列 丁

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接微信公众号「ENG八戒」https://mp.weixin.qq.com/s/PP9b5cSNd-7IqgNovcrB0A 优化输出 前面已经对 cargo package 工程编译输出了好多遍&#xff0c;发现编译结果打印的信息都包含了这个 unoptimize…

c语言:[输出函数]与[输入函数]|要点简述

一、【输出函数】 printf() 与 puts()的不同点 1、printf()函数 printf()支持单个字符%c的输出&#xff0c;以及字符串%s的输出。 (1)如果是以%c的形式输出&#xff0c;是一个字符一个字符的输出。因此&#xff0c;要用一个循环语句&#xff0c;把字符逐个输出。 (2)而用%…

Unity与Android交互通信系列(2)

在上一篇文章中&#xff0c;我们介绍了Unity和Android交互通信的原理及在Unity中直接调用Java代码的方式&#xff0c;但没有给出代码示例&#xff0c;下面通过实际例子演示上篇文章中AndroidJavaClass、AndroidJavaObject两个类的基本用法&#xff0c;由于交互通信涉及到两端&a…

查看知乎数学公式Tex源码的方法

首先使用F12打开开发者工具&#xff0c;再使用元素选择器选中要查看的公式。 在源码对应位置附近可以看到一个类型为 math/tex 的 script&#xff0c;如果没找到可以展开目录查找。

网络时间服务器

本章主要介绍网络时间服务器。 使用chrony配置时间服务器 配置chrony客户端向服务器同步时间 1 时间同步的必要性 一些服务对时间要求非常严格&#xff0c;例如&#xff0c;图所示的由三台服务器搭建的ceph集群。 这三台服务器的时间必须保持一致&#xff0c;如果不一致&#…

若依打包将vue放到.jar里面部署

1.vue静态文件&#xff0c;以及单页面 ruoyi-admin\src\main\resources\static \ruoyi-admin\src\main\resources\templates 2.后台开放白名单 "/cms", "/cms#/login" 3. mvc访问vue页面入口&#xff0c;接口 package com.ruoyi.web.controller.syst…

08_CSS定位与综合案例开发

day08_CSS定位与&综合案例开发 Objective&#xff08;本课目标&#xff09; 理解什么是定位能说出为什么要用定位 1. 为什么使用定位 标准流在最底层 (海底) ------- 浮动的盒子在中间层 (海面) ------- 定位的盒子 在 最上层 &#xff08;天空&#xff09; 小黄色块在…

软件测试面试八股文,最常见的7个高频面试题(附答案,建议收藏)

问题1&#xff1a;请自我介绍下&#xff1f; 核心要素&#xff1a;个人技能优势工作背景经验亮点 参考回答&#xff1a; 第一种&#xff1a;基本信息离职理由 面试官您好&#xff0c;我叫张三&#xff0c;来自番茄市&#xff0c;在软件测试⾏业有 3 年的⼯作经验。做过 Web…

算法——动态规划(DP,Dynamic Programming)

一、基础概念 DP的思想&#xff1a; 把问题分成子问题&#xff0c;前面子问题的解决结果被后面的子问题使用DP与分治法的区别&#xff1a; 分治法把问题分成独立的子问题&#xff0c;各个子问题能独立解决 自顶向下DP前面子问题的解决结果被后面的子问题使用&#xff0c;子问题…

3090K MOSFET N通道沟槽功率 PWM应用

3090K 采用沟槽技术&#xff0c;提供活x氧(导通)&#xff0c;低栅J电荷和栅J电压低至4.5V的工作。3090K 设备适用于各种应用。 3090K 特性&#xff1a; ● VDS 30V,ID 86A RDS(ON) < 5 mΩ VGS 10V RDS(ON) < 9.5mΩ VGS 4.5V ● 高功率和电流处理能力 ● 获得无…

【性能测试】基础知识篇-压力模型

常见压力模式 并发模式&#xff08;即虚拟用户模式&#xff09;和RPS模式&#xff08;即Requests Per Second&#xff0c;每秒请求数&#xff0c;吞吐量模式&#xff09;。 本文介绍这两种压力模式的区别&#xff0c;以便根据自身业务场景选择更合适的压力模式。 并发模式 …

C++面向对象(OOP)编程-模板

本文主要讲解C的模板&#xff0c;其中包括模板的分类&#xff0c;函数模板和类模板&#xff0c;以及类模板与友元函数关系引起的几种关系。强调提供代码来搞懂C模板这一泛型编程手段。 目录 1 C模板 2 模板的本质 3 模板分类 4 函数模板 4.1 函数模板定义格式 4.2 函数模…