关于CountDownLatch失效问题

       一、项目背景

         这几天要开发一个类似支付宝那种年度账单统计的功能,就是到元旦后支付完会把用户这一年的消费情况从各个维度(我们把这一个维度称作一个指标)统计分析形成一张报告展示给用户。

        这个功能实现用到了CountDownLatch。假如统计分析用户的年底消费账单是10个指标。则希望用10线程并发去分别统计这10个指标,等10个线程都完成计算后,最后在通过另外一个线程汇总10个指标返给前端展示给用户。

        二、问题描述

        其中出现了这样一个问题,生成第一个用户的年度账单是10个指标计算完后,最后一个线程进行最后的结果统计。这没问题。但是在生成第二个用户年底账单时,返给前端的是空。但是数据库里却生成了第二用户的年度账单。后面生成的所有用户年度账单都是空,且数据库都有每个用户的账单。

        三、错误代码示例

        

package com.lsl.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;@Controller
@RequestMapping("/latch")
public class CountDownLatchController {//创建固定线程池最大线程数10private static ExecutorService executor = Executors.newFixedThreadPool(10);//模拟并发任务数private static   int taskNum = 10;//计数器CountDownLatch latch = new CountDownLatch(taskNum);@PostMapping(value = "execTask", produces = "application/json;charset=UTF-8")@ResponseBodypublic String execTask(){for (int i = taskNum;i>=1;i--){String name = "thread";Future<Map> submit = executor.submit(new CountNumTask(latch,name, i));
//            try {
//                Map map = submit.get();
//                String ThreadName = map.get("name").toString();
//                String total = map.get("total").toString();
//                System.err.println("ThreadName:" + ThreadName + ",total=" + total);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            } catch (ExecutionException e) {
//                e.printStackTrace();
//            }}try {latch.await(10, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}//正常情况下,等10个任务执行完毕下面的主线程才输出System.out.println("主线程开始执行了.....");return "success";}/*** 线程任务*/private  class CountNumTask implements Callable<Map>{private String name;private int num;private CountDownLatch latch;public CountNumTask(CountDownLatch latch,String name,int num){this.latch = latch;this.name = name;this.num = num;}@Overridepublic Map call() throws Exception {long st = new Date().getTime();Map resultMap = new HashMap();String threadName = name + num;resultMap.put("name",threadName);int total = 0;for (int i =0;i<=num;i++){total += i;}Thread.sleep(num+100);//每个任务sleep不同,模拟线程耗时不一样resultMap.put("total",total);long ed = new Date().getTime();System.err.println("ThreadName:" + threadName + ",total=" + total + ",耗时=" + (ed-st));latch.countDown();return resultMap;}}
}

第一次调用截图:

第二次调用截图:

   

从上面截图可以看出,10个指标线程还没有运行完,主线程就先输出了。

四、原因分析

        原来是CountDownLatch latch = new CountDownLatch(taskNum);定义成成员变量了。这个应用定义成局部变量,也就是放在方法内。

        原因是spring托管的bean都是单例的,第一次调用结束后latch.getCount()已经是0了,然后后面的调用就不会等待前面子任务完成就开始执行主线程任务了。这就是为什么数据库里有每次的数据,而没有返给前端的原因。

        网上有的说法是错误:他们认为是线程内的latch.countDown();没有执行,应该把这个放在fianlly语句快内,保证改计数器减1操作每次都能执行。

        如果是这样那么计数器没有到0,如果在方法内latch.await(10, TimeUnit.SECONDS);这个语句就可以看出,10秒钟后主线程也会执行,那么上面的10个线程如果每个任务的耗时都超过10秒才能出现主线程比子任务输出早的情况。如果采用的是latch.await();那么主线程就会被永远阻塞了,因为计数器没有到0。这个前提是CountDownLatch latch = new CountDownLatch(taskNum)这个定义的是局部变量。

五、正确的代码示例

package com.lsl.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;@Controller
@RequestMapping("/latch")
public class CountDownLatchController {//创建固定线程池最大线程数10private static ExecutorService executor = Executors.newFixedThreadPool(10);//模拟并发任务数private static   int taskNum = 10;@PostMapping(value = "execTask", produces = "application/json;charset=UTF-8")@ResponseBodypublic String execTask(){//计数器CountDownLatch latch = new CountDownLatch(taskNum);for (int i = taskNum;i>=1;i--){String name = "thread";Future<Map> submit = executor.submit(new CountNumTask(latch,name, i));
//            try {
//                Map map = submit.get();
//                String ThreadName = map.get("name").toString();
//                String total = map.get("total").toString();
//                System.err.println("ThreadName:" + ThreadName + ",total=" + total);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            } catch (ExecutionException e) {
//                e.printStackTrace();
//            }}try {latch.await(10, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}//正常情况下,等10个任务执行完毕下面的主线程才输出System.out.println("主线程开始执行了.....");return "success";}/*** 线程任务*/private  class CountNumTask implements Callable<Map>{private String name;private int num;private CountDownLatch latch;public CountNumTask(CountDownLatch latch,String name,int num){this.latch = latch;this.name = name;this.num = num;}@Overridepublic Map call() throws Exception {long st = new Date().getTime();Map resultMap = new HashMap();String threadName = name + num;resultMap.put("name",threadName);int total = 0;for (int i =0;i<=num;i++){total += i;}Thread.sleep(num+100);//每个任务sleep不同,模拟线程耗时不一样resultMap.put("total",total);
//            if (num!=5)long ed = new Date().getTime();System.err.println("ThreadName:" + threadName + ",total=" + total + ",耗时=" + (ed-st));latch.countDown();return resultMap;}}
}

两次调用截图:

六、其他几点细节

        细节1、从代码分析,thread10是第一进入进入线程的,为什么确实最后进入线程的thread1先输出了呢?原因从截图中我打印的耗时就能看出来,就是thread10耗时最长,所以最晚输出。

        细节2、如果我把下图的代码放开,且把计数器还定义成成员变量,会有什么结果呢?(结果可能出乎大家意料很好玩哦

        

我把代码附下面:

package com.lsl.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;@Controller
@RequestMapping("/latch")
public class CountDownLatchController {//创建固定线程池最大线程数10private static ExecutorService executor = Executors.newFixedThreadPool(10);//模拟并发任务数private static   int taskNum = 10;//计数器CountDownLatch latch = new CountDownLatch(taskNum);@PostMapping(value = "execTask", produces = "application/json;charset=UTF-8")@ResponseBodypublic String execTask(){for (int i = taskNum;i>=1;i--){String name = "thread";Future<Map> submit = executor.submit(new CountNumTask(latch,name, i));try {Map map = submit.get();String ThreadName = map.get("name").toString();String total = map.get("total").toString();System.err.println("ThreadName:" + ThreadName + ",total=" + total);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}try {latch.await(10, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}//正常情况下,等10个任务执行完毕下面的主线程才输出System.out.println("主线程开始执行了.....");return "success";}/*** 线程任务*/private  class CountNumTask implements Callable<Map>{private String name;private int num;private CountDownLatch latch;public CountNumTask(CountDownLatch latch,String name,int num){this.latch = latch;this.name = name;this.num = num;}@Overridepublic Map call() throws Exception {long st = new Date().getTime();Map resultMap = new HashMap();String threadName = name + num;resultMap.put("name",threadName);int total = 0;for (int i =0;i<=num;i++){total += i;}Thread.sleep(num+100);//每个任务sleep不同,模拟线程耗时不一样resultMap.put("total",total);
//            if (num!=5)long ed = new Date().getTime();System.err.println("ThreadName:" + threadName + ",total=" + total + ",耗时=" + (ed-st));latch.countDown();return resultMap;}}
}

运行截图如下图:

        从上面截图是不是发现了很奇怪啊!计数器定义成了成员变量,第二次调用为什么主线程是等前面10子任务都完成了才输出呢?而且子任务的输出顺序也对了,是从thread10到thread1依次输出,虽然thread10耗时最长,也是第一个输出了!!!

        出现上述2个反常,大家知道什么原因吗?欢迎在评论区留言!!!

        具体原因我会过几天在公布吧!!!!

        【因为submit.get()方法会依次获取线程的结果。而不是先获取到最新执行完的线程结果

        细节3、如果把线程内的latch.countDown()位置调整到最开始位置,会出现什么结果呢?

        如下图:

        

运行结果截图如下:

从细节3的现象可以看出,latch.countDown()位置放到线程任务的最后面,这个很重要。因为在latch.wait()实时读取计数器的数值是否到0了,一旦到0了,后面的主线程就里面执行了。这就和另外的CyclicBarrier(循环栅栏)有所区别了。

        细节4:latch.countDown()一定要放在线程内所有任务完成之后。

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

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

相关文章

ImportError: cannot import name ‘packaging‘ from ‘pkg_resources‘ 的参考解决方法

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; Ubuntu20.04 ROS-Noetic 一、问题描述 自己在通过 pip install 安装module时 &#xff08;使用的是 pip install mmcv&#xff09;遇到如下问题&#xff1a; ImportError: cannot …

「Mac畅玩鸿蒙与硬件30」UI互动应用篇7 - 简易计步器

本篇将带你实现一个简易计步器应用&#xff0c;用户通过点击按钮增加步数并实时查看步数进度&#xff0c;目标步数为 10000 步。该项目示例展示了如何使用 Progress 组件和 Button 组件&#xff0c;并结合状态管理&#xff0c;实现交互式应用。 关键词 UI互动应用计步器Button…

多媒体信息检索

文章目录 一、绪论二、文本检索 (Text Retrieval)(一) 索引1.倒排索引2.TF-IDF (二) 信息检索模型 (IR模型&#xff0c;Information Retrieval)1.布尔模型 (Boolean模型)(1)扩展的布尔模型 (两个词)(2)P-Norm模型 (多个词) 2.向量空间模型 (Vector Space Model&#xff0c;VSM)…

在vscode中如何利用git 查看某一个文件的提交记录

在 Visual Studio Code (VSCode) 中&#xff0c;你可以使用内置的 Git 集成来查看某个文件的提交历史。以下是具体步骤&#xff1a; 使用 VSCode 内置 Git 功能 打开项目&#xff1a; 打开你的项目文件夹&#xff0c;确保该项目已经是一个 Git 仓库&#xff08;即项目根目录下…

vue2 -- el-form组件动态增减表单项及表单项验证

需求 在数据录入场景(如订单信息录入)中,可根据实际情况(如商品种类增加)动态添加表单项(如商品相关信息)。包含必填项验证和数据格式验证(如邮箱、电话格式),防止错误数据提交。 效果 代码一 <template><div>

使用 Elasticsearch 构建食谱搜索(一)

作者&#xff1a;来自 Elastic Andre Luiz 了解如何使用 Elasticsearch 构建基于语义搜索的食谱搜索。 简介 许多电子商务网站都希望增强其食谱搜索体验。正确使用语义搜索可以让客户根据更自然的查询&#xff08;例如 “something for Valentines Day - 情人节的礼物” 或 “…

SystemVerilog学习笔记(七):函数与任务

函数 函数的主要用途是编写一段可以随时调用n次的代码&#xff0c;只需调用函数名即可&#xff0c;不需要任何模拟时间来执行。函数是返回类型&#xff0c;仅返回函数声明中提到的单个值&#xff0c;如果未声明则返回一个位的值。 语法&#xff1a; initial begin functio…

物理验证Calibre LVS | SMIC Process过LVS时VNW和VPW要如何做处理?

SMIC家工艺的数字后端实现PR chipfinish写出来的带PG netlist如下图所示。我们可以看到标准单元没有VNW和VPW pin的逻辑连接关系。 前几天小编在社区星球上分享了T12nm ananke_core CPU低功耗设计项目的Calibre LVS案例&#xff0c;就是关于标准单元VPP和VBB的连接问题。 目前…

纯前端实现在线预览excel文件(插件: LuckyExcel、Luckysheet)

概述 在实际开发中&#xff0c;遇到需要在线预览各种文件的需求&#xff0c;最近遇到在线预览excel文件的需求&#xff0c;在此记录一下&#xff01;本文主要功能实现&#xff0c;用于插件 LuckyExcel &#xff0c;Luckysheet&#xff01;废话不多说&#xff0c;上代码&#xf…

LocalDate和LocalDateTime类

在Java 8中引入的LocalDate表示一个格式为yyyy-MM-dd的日期&#xff0c;如2024-06-13。它不存储时间或时区。我们可以从LocalDate中获取许多其他的日期字段&#xff0c;如年日(day-of-year)、周日(day-of-week)、月日(month-of-year)等等。 1 初始化 LocalDate以年月日的格式输…

信息安全工程师(82)操作系统安全概述

一、操作系统安全的概念 操作系统安全是指操作系统在基本功能的基础上增加了安全机制与措施&#xff0c;从而满足安全策略要求&#xff0c;具有相应的安全功能&#xff0c;并符合特定的安全标准。在一定约束条件下&#xff0c;操作系统安全能够抵御常见的网络安全威胁&#xff…

小程序源码-模版 100多套小程序(附源码)

一、搭建开发环境 搭建环境可以从这里开始&#xff1a; 微信小程序从零开始开发步骤&#xff08;一&#xff09;搭建开发环境 - 简书 二、程序示例 1、AppleMusic https://download.csdn.net/download/m0_54925305/89977187 2、仿B站首页 https://download.csdn.net/downlo…

安装baidubce库

直接pip install baidubce会带来一系列后续文件缺失问题&#xff0c;应该&#xff1a; pip install bce-python-sdk

【Java】-- 异常

1. 异常的概念与体系结构 1.1 异常的概念 在Java中&#xff0c;将程序执行过程中发生的不正常行为称为异常。 public class Test {public static void main(String[] args) {//算术&#xff08;ArithmeticException&#xff09;异常 // int a 5/0; // System.…

从零开始:利用Portainer CE和cpolar搭建NextCloud私有云存储

文章目录 前言1. 在PortainerCE中创建NextCloud容器2. 公网远程访问本地NextCloud容器2.1 内网穿透工具安装3.2 创建远程连接公网地址 3. 固定NextCloud私有云盘公网地址 前言 本文将介绍如何在本地利用Portainer CE的可视化界面创建NextCloud私有云盘容器&#xff0c;并通过c…

[安洵杯 2019]easy_web 详细题解

知识点: 编码转换 命令执行 linux空格_关键字绕过 打开页面 发现url 是 /index.php?imgTXpVek5UTTFNbVUzTURabE5qYz0&cmd 有img参数和cmd参数 cmd参数是没赋值的,随便赋值为123456 页面没有反应 鼠标移动到图片下面时发现有东西,当然直接查看页面源代码也可以发现 尝…

第2章 数据的表示和运算

王道学习 考纲内容 &#xff08;一&#xff09;数制与编码 进位计数制及其相互转换&#xff1b;定点数的编码表示 &#xff08;二&#xff09;运算方法和运算电路 基本运算部件&#xff1a;加法器&#xff1b;算术逻辑单元&#xff08;ALU&#xff09;…

Web3 游戏周报(11.03 - 11.09)

回顾上周的区块链游戏概况&#xff0c;查看 Footprint Analytics 与 ABGA 最新发布的数据报告。 【11.03 - 11.09】Web3 游戏行业动态&#xff1a; Ton Accelerator 推出名为「Synergy」的 500 万美元计划&#xff0c;旨在推动跨链创新&#xff0c;创造 TON 用户与 EVM 网络适应…

数据分析:16s差异分析DESeq2 | Corncob | MaAsLin2 | ALDEx2

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍DESeq2原理计算步骤结果Corncob原理计算步骤结果MaAsLin2原理计算步骤结果ALDEx2原理计算步骤结果加载R包数据链接数据预处理微生物数据样本信息提取物种名称过滤零值保留结果读取…

H.264/H.265播放器EasyPlayer.js无插件H5播放器关于WASM的压缩优化

在当今的Web开发领域&#xff0c;流媒体播放器的性能和效率至关重要&#xff0c;尤其是在处理大型视频文件和高分辨率视频流时。EasyPlayer.js RTSP播放器作为一款先进的流媒体播放器&#xff0c;它在WebAssembly&#xff08;WASM&#xff09;的压缩优化方面表现出色&#xff0…