关于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);latch.countDown();long ed = new Date().getTime();System.err.println("ThreadName:" + threadName + ",total=" + total + ",耗时=" + (ed-st));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)latch.countDown();long ed = new Date().getTime();System.err.println("ThreadName:" + threadName + ",total=" + total + ",耗时=" + (ed-st));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)latch.countDown();long ed = new Date().getTime();System.err.println("ThreadName:" + threadName + ",total=" + total + ",耗时=" + (ed-st));return resultMap;}}
}

运行截图如下图:

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

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

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

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

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

        如下图:

        

运行结果截图如下:

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

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【含开题报告+文档+源码】基于SSM的物流管理系统设计与实现

开题报告 随着电子商务的迅猛发展和人们生活水平的提高&#xff0c;快递服务行业正经历着前所未有的增长。占航快递公司作为国内知名的快递企业之一&#xff0c;面临着巨大的机遇和挑战。传统的快递服务管理方式已经无法满足日益增长的业务需求&#xff0c;快递服务流程中的问…

【AtCoder】Beginner Contest 377-C.Avoid Knight Attack

Avoid Knight Attack 题目链接 Problem Statement There is a grid of N 2 N^2 N2 squares with N N N rows and N N N columns. Let ( i , j ) (i,j) (i,j) denote the square at the i i i-th row from the top ( 1 ≤ i ≤ N ) (1\leq i\leq N) (1≤i≤N) and j j…

sizeof和strlen区分,(好多例子)

sizeof算字节大小 带\0 strlen算字符串长度 \0之前

Javascript中如何实现函数缓存?函数缓存有哪些应用场景?

#一、是什么 函数缓存&#xff0c;就是将函数运算过的结果进行缓存 本质上就是用空间&#xff08;缓存存储&#xff09;换时间&#xff08;计算过程&#xff09; 常用于缓存数据计算结果和缓存对象 解释 const add (a,b) > ab; const calc memoize(add); // 函数缓存…

MATLAB实现智能水滴算法(Intelligent Water Drops Algorithm, IWDA)

1.智能水滴算法介绍 智能水滴算法&#xff08;Intelligent Water Drops Algorithm&#xff0c;IWDA&#xff09;是一种基于水滴特性的智能优化算法&#xff0c;它借鉴了水滴在自然界中的运动和形态变化规律&#xff0c;通过模拟水滴的形成、发展和消亡过程&#xff0c;实现问题…

(Go基础)Go的运行流程步骤与包的概念

1. 快速入门 所有的go开发&#xff0c;都必须存在并包含在某一个包内 .go 是go语言程序的后缀名 1.1 编译 通过使用 go build 命令对该go文件进行编译&#xff0c;生成.exe文件 1.2 运行 运行刚刚生成出来的test.exe文件既可&#xff0c;不过并不不是双击&#xff0c;而是在…

AI 写作(三)文本生成算法:创新与突破(3/10)

一、生成式与判别式模型&#xff1a;AI 写作的基石 &#xff08;一&#xff09;区别与特点 生成式模型和判别式模型在多个方面存在明显差异。在优化准则上&#xff0c;生成式模型致力于学习联合概率分布&#xff0c;而判别式模型则专注于建立输入数据和输出之间的关系&#xf…

ubuntu下使用pocketsphinx进行语音识别(包含交叉编译)

文章目录 前言一、pocketsphinx的介绍二、ubuntu下编译三、使用示例1.模型选择2.代码示例3.自定义字典 四、交叉编译总结 前言 由于工作需要语音识别的功能&#xff0c;环境是在linux arm版上&#xff0c;所以想先在ubuntu上跑起来看一看&#xff0c;就找了一下语音识别的开源…

中国自主品牌荣耀时刻:海豹荣获欧洲车身大奖

近日&#xff0c;在德国巴特瑙海姆举行的2024欧洲车身大会上&#xff0c;比亚迪海豹凭借其卓越的车身架构设计、创新技术和美学设计&#xff0c;一举斩获了本次大赛第三名的殊荣。 这不仅是中国自主品牌在欧洲车身大会上的首次获奖&#xff0c;而且也是比亚迪技术创新与实力在国…

RocketMQ 广播消息

所谓的广播消息就是发送的一条消息会被多个消费者收到。 ⼴播是向主题&#xff08; topic &#xff09;的所有订阅者发送消息。订阅同⼀个 topic 的多个消费者&#xff0c;能全量收到⽣产者发送的所有消息。 生产者发送了10个order&#xff0c;每个order里面有5个消息&#xff…

如何实现智慧园区的节能降耗?

江园科技智慧园区实现智慧园区节能降耗可以从以下几个方面入手&#xff1a; 能源监测与管理系统 - 安装智能电表、水表和气表等设备&#xff0c;实时精准地监测园区内各区域、各企业及各设备的能源消耗情况&#xff0c;如电量的峰谷时段使用量、用水量的波动等。这些数据会传输…

索引【MySQL】

文章目录 聚簇索引 VS 非聚簇索引索引MySQL与磁盘交互的基本单位主键索引索引操作唯一索引的创建普通索引的创建复合索引 索引创建原则 聚簇索引 VS 非聚簇索引 MyISAM存储引擎 - 主键索引结构 MyISAM存储引擎同样采用B树作为索引的基本数据结构 与InnoDB存储引擎的B树不同的…

CDH大数据平台部署

二、CDH简介 全称Cloudera’s Distribution Including Apache Hadoop。 hadoop的版本 (Apache、CDH、Hotonworks版本) 在公司中一般使用cdh多一些&#xff08;收费的&#xff09;、也有公司使用阿里云大数据平台、微软的大数据平台。 国内也有一些平台&#xff1a;星环大数…

如何删除苹果手机所有照片:彻底清理指南

随着时间的推移&#xff0c;我们的iPhone往往会积累大量照片&#xff0c;这些照片占据了大量存储空间并可能影响设备性能。有时&#xff0c;为了快速释放空间或重置手机内容&#xff0c;您可能需要了解如何删除苹果手机所有照片。别担心&#xff0c;本文将向你讲解如何删除苹果…

JAVA开源项目 服装销售平台 计算机毕业设计

博主说明&#xff1a;本文项目编号 T 054 &#xff0c;文末自助获取源码 \color{red}{T054&#xff0c;文末自助获取源码} T054&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…

Spire.PDF for .NET【页面设置】演示:获取 PDF 文件中的页数

计算 PDF 文件中的页数对于各种目的都至关重要&#xff0c;例如确定文档长度、组织内容和评估打印要求。除了使用 PDF 查看器了解页数信息外&#xff0c;您还可以通过编程自动执行该任务。在本文中&#xff0c;您将学习如何使用C#通过Spire.PDF for .NET获取 PDF 文件中的页数。…

面试总结!

OSI七层模型&#xff1a; 什么是OSI七层模型&#xff1f; 我们需要了解互联网的本质是一系列的网络协议&#xff0c;这个协议就叫做OSI协议&#xff08;开放系统互联(Open System Interconnection&#xff09;&#xff09;&#xff0c;它是由ISO&#xff08;国际标准化组织&…

LeetCode:102. 二叉树的层序遍历(java)

目录 题目描述: 代码: 第一种: 第二种: TreeNode: LinkedListNode: 题目描述: 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,…

R语言实战——一些批量对地理数据进行操作的方法

各位朋友在进行数据处理时&#xff0c;当有多张栅格影像时&#xff0c;如果我们都要进行同一操作时&#xff0c;一张一张做很繁琐&#xff0c;用ArcGIS模型构建器是一种比较好的方法。当然&#xff0c;今天小编新学了R语言上面进行批量裁剪&#xff0c;一起来学习一下吧&#x…

TEMU测评:在挑战与机遇中寻求突破

近年来&#xff0c;TEMU&#xff08;由拼多多推出的海外电商平台&#xff09;在全球范围内迅速崛起&#xff0c;特别是在美国市场&#xff0c;其以极具竞争力的价格和丰富的商品种类吸引了大量海外消费者。然而&#xff0c;随着市场竞争的加剧和外部环境的变化&#xff0c;TEMU…