一个countDown在多线程调度下使用不当的分享

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

一个countDown在多线程调度下使用不当的分享

1. 诡异的数据抖动

在一个需求开发过程中,由于有多角色需要获取每个角色下的菜单;结果出现了单角色下拉去菜单没问题,多角色情况下只有一个角色的菜单正常返回的问题。这个问题很忧伤,没有菜单如何进功能页面?

2. 怀疑是缓存

因为多角色下菜单采用了Redis缓存,故而怀疑是其中一个角色下的菜单是缓存失效,但是关闭掉缓存依然不起作用,排除缓存影响。

3. debug发现问题

通过增加日志输出,在关闭掉缓存的实时模式下,依然存在菜单时而有,时而没有的情况。证明应该是代码有问题。

在一个多线程调度调度服务类中,发现一个问题,即在debug到如下代码,会出现后续代码未执行完全,接口结果即被返回的情况。

//经过查询相关API,不会出现时序问题,可放心使用final CountDownLatch latch = new CountDownLatch(callableList.size());for(Callable callable :callableList){ListenableFuture<T> listenableFuture = threadPoolTaskExecutor.submitListenable(callable);listenableFuture.addCallback(new ListenableFutureCallback<T>() {@Overridepublic void onFailure(Throwable throwable) {//过早调用countDown BUGlatch.countDown();LogHelper.EXCEPTION.error("执行任务异常",throwable);if(futureCallback!=null){futureCallback.onFailure(throwable);}}@Overridepublic void onSuccess(T t) {//过早调用countDown BUGlatch.countDown();if(futureCallback!=null){futureCallback.onSuccess(t);}}});}

4. countDown调用时机不对

在调用远程接口返回后,立即执行 lacth.countDown(); 会立刻造成主线程阻塞释放,立即响应结果,丢失部分数据。如下图所示,在第二个任务获取数据处理完成后, 就立即调用latch.countDown(), 致使后续的回调还未执行。主线程在收到 latch的释放阻塞后,返回了不完整的数据结果。

错误的调用时序

改造后,将countDown放在回调执行完成之后,并放置在 try {} finally { }代码块之中,保证一定得到执行,防止抛异常后,主线程阻塞。 如下所示:

        final CountDownLatch latch = new CountDownLatch(callableList.size());for(Callable callable :callableList){ListenableFuture<T> listenableFuture = threadPoolTaskExecutor.submitListenable(callable);listenableFuture.addCallback(new ListenableFutureCallback<T>() {@Overridepublic void onFailure(Throwable throwable) {try{LogHelper.DEFAULT.info("多线程回调,latchCount="+latch.getCount());LogHelper.EXCEPTION.error("执行任务异常",throwable);if(futureCallback!=null){futureCallback.onFailure(throwable);}}finally {latch.countDown();}}@Overridepublic void onSuccess(T t) {try{LogHelper.DEFAULT.info("多线程回调成功,latchCount="+latch.getCount());if(futureCallback!=null){futureCallback.onSuccess(t);}}finally {latch.countDown();}}});}

5. 本次使用的多线程调度说明

使用Future阻塞模式,不会出现以上问题,使用future.get()的阻塞式获取,不需要CountDownLatch工具类配合使用。而且本次其实也可以使用Future来实现同样的功能。但是Future没有提供 onFailure, onSucess 这样的回调接口,考虑到易用性,采用了ListenableFuture;

本次采用的 spring的 ListenableFuture 方式回调来实现回调式聚合。在对CountDownLatch使用不当的情况下,出现了该问题。解决该问题后,功能运行正常。

本次提供的服务类 ThreadPoolExecutorService, 其主要的方法如下:


/*** 线程池服务类** @author David* @since 2018/5/15*/
public interface ThreadPoolExecutorService {/*** 执行多线程任务处理,并通过 futureCallback对结果进行回调处理* @param callableList  异步线程可执行的任务 List* @param futureCallback  异步回调* @param timeout 超时时间,毫秒* @param <T>*/<T> void execute(List<Callable<T>> callableList, ListenableFutureCallback<T> futureCallback, long timeout);/*** 执行多线程任务处理,并将结果聚合成一个List 进行返回** @param callableList   异步线程可执行的任务 List* @param failureCallback  失败的回调方法* @param skipNull        是否忽略Null 结果,如果忽略 null 不会添加到List 之中* @param timeout 超时时间,毫秒* @param <T>*/<T> List<T> executeAndMerge(List<Callable<T>> callableList, FailureCallback failureCallback, boolean skipNull, long timeout);/*** 执行多线程任务处理,并将结果List,聚合成一个List 进行返回** @param callableList 异步线程可执行的任务 List* @param failureCallback 失败的回调方法* @param timeout 超时时间,毫秒* @param <T>*/<T> List<T> executeAndMergeList(List<Callable<List<T>>> callableList, FailureCallback failureCallback, long timeout);

6. Future 和 ListenableFuture的区别

spring 或者 guava 的 ListenableFutrue其使用方式和机理应该是类似的,这里的说明是通用的。本次工具类使用的是spring自带的ListenableFutrue。

6.1 区别说明

ListenableFuture顾名思义就是可以监听的Future,它是对java原生Future的扩展增强。我们知道Future表示一个异步计算任务,当任务完成时可以得到计算结果。如果我们希望一旦计算完成就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态。这样做,代码复杂,而且效率低下。使用ListenableFuture帮我们检测Future是否完成了,如果完成就自动调用回调函数,这样可以让主线程不必阻塞,减少并发程序的复杂度。

6.4 spring ListenableFuture使用示例

一个简单的示例,如果要获得 ListenableFuture,则需要一个对Java线程池进行修饰过的线程池执行器。如下所示:

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><property name="corePoolSize" value="${threadpool.corePoolSize}" /><property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" /><property name="maxPoolSize" value="${threadpool.maxPoolSize}" /><property name="queueCapacity" value="${threadpool.queueCapacity}" /><property name="rejectedExecutionHandler"><bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /></property></bean>

guava的请参考以下方式进行修饰,这里不进行详细说明:

   //Java线程池的类型是可选的【根据场景自行构建】   ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

得到修饰过的线程池执行器后,即可提交可Callable任务,得到 ListenableFuture 进行处理。

以下为计算1~5的3次方的结果并输出,不需要聚合结果,每个线程计算完毕后,立刻输出结果:

for(int i=1; i<=5; i++){final int num = i;ListenableFuture<Integer> listenableFuture = threadPoolTaskExecutor.submitListenable(new Callable<Integer>() {@Overridepublic Integer call(){return (int)Math.pow(num,3);}});listenableFuture.addCallback(new ListenableFutureCallback<Integer>() {@Overridepublic void onFailure(Throwable throwable) {LogHelper.EXCEPTION.error("处理失败", throwable);}@Overridepublic void onSuccess(Integer result) {System.out.println(num + "的3次方为:"+ result);}});}

6.2 是否可以添加多个callback

答案是肯定的,因为ListenableFuture 的addCallback是添加到ListenableFutureCallbackRegistry 一个注册中心。而注册中心底层是支持多个回调的。在6.3会具体介绍回调方法注册中心的处理逻辑。

 public void addCallback(ListenableFutureCallback<? super T> callback) {this.callbacks.addCallback(callback);}

6.3 addCallback是否需要考虑时序

guava 和 spring的 ListenableFuture 均做了时序兼容,在listenableFuture执行的任意时刻调用 addCallback 均可准确的执行回调。这里就不得不说在调用addCallback的时候,其实将回到方法注册到ListenableFutureCallbackRegistry(回调注册中心)。

6.3.1 ListenableFutureCallbackRegistry的特性有:

a. 记录了Callable的3种调度状态:
NEW(新建,还未执行),SUCCESS(返回成功),FAILURE(抛异常,失败)

b. 有两个处理队列,分别是:
successCallbacks:存储执行成功的回调方法;
failureCallbacks: 存储执行失败的回调方法(抛异常)。

c. 存储响应结果
将Callable<T> 返回的结果,也放在回调中心里面;

d. mutex 对象保证线程安全
有一个成员变量:private final Object mutex; 用来保证在添加回调任务,或者设置结果集的时候,注册中心是线程安全的。

其在添加回调任务的时候,处理流程为: call调用时序

 synchronized(this.mutex) {...}

如上图所示,在添加回调任务的时候,会通过synchronized先获取 mutex 排它锁,保证处理的线程安全。继而判断任务的状态:

如果为NEW,标识任务还未执行完毕,这时候需要将任务先放入队列,待任务执行完毕再根据状态调用回调方法;

如果为SUCCESS,标识任务已经执行成功,不需要再放入队列,而是在当前线程中,直接调用 onSuccess方法;

如果为FAILURE, 标识任务已经执行失败,不需要再放入队列,而是在当前线程中,直接调用 onFailure方法;

此外,还有一个流程,即在Callable任务调度完成,返回结果后,如果未抛异常:对successCallbacks队列中的方法进行逐个回调;如果抛出异常,对failureCallbacks队列中的方法进行逐个回调。因流程较简单,这里只是简单说明。

6.4 说明

本文只分析到 ListenableFuture 的使用方式, CountDownLatch的调用时机和ListenableFuture 的特性。

因时间和篇幅有限,具体spring 或 guava在submitListenable 任务之后,内部处理逻辑并没有阐述。感兴趣的同学可以具体到源码查看其内部实行逻辑。

转载于:https://my.oschina.net/davidzhang/blog/1839206

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

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

相关文章

linux脚本打印循环次数,shell脚本编程基础(3)——循环用法

本节索引&#xff1a;一、if、case条件判断二、for、while及until循环三、循环控制语句continue、break、shift及select菜单四、信号捕捉trap在前面的基础编程内容中&#xff0c;我们已经学习了shell脚本的顺序执行及选择执行&#xff0c;通过这两种方式&#xff0c;可以帮我们…

EF CORE 7 中的新功能:使用 ExecuteDelete 和 ExecuteUpdate 进行批量操作

原文链接&#xff1a;https://timdeschryver.dev/blog/new-in-entity-framework-7-bulk-operations-with-executedelete-and-executeupdate原文作者&#xff1a;tim_deschryver翻译&#xff1a;沙漠尽头的狼(谷歌翻译加持)Entity Framework 7 包括一些已被要求的流行功能&#…

java 简单json和对象相互转换

2019独角兽企业重金招聘Python工程师标准>>> package Fasterxml; import com.fasterxml.jackson.databind.ObjectMapper; import mode.User; import java.io.StringWriter; import java.util.ArrayList; import java.util.List;/*** maven...**<dependency>* …

畅想动画制作的乐趣

为什么要制作动画&#xff1f; 现在的营销活动&#xff0c;用一个很简单的图片去吸引消费者已经远远不够。想让消费者创造GMV&#xff0c;肯定需要让消费者觉得眼前一亮或是有视觉冲击的东西&#xff0c;或者在动画过程中提供更好的引导部分&#xff0c;比如红包&#xff0c;引…

Spring Cloud Gateway 原生支持接口限流该怎么玩

关于pig&#xff1a; 基于Spring Cloud、oAuth2.0开发基于Vue前后分离的开发平台&#xff0c;支持账号、短信、SSO等多种登录&#xff0c;提供配套视频开发教程。 关于 Spring Cloud Gateway SpringCloudGateway是Spring官方基于Spring 5.0&#xff0c;Spring Boot 2.0和Projec…

我的手机 不支持箭头函数

不支持&#xff0c;要换成function的形式 转载于:https://www.cnblogs.com/web-fusheng/p/7295901.html

中标麒麟linux卸载qt,国产化 银河麒麟编译Qt程序的问题汇总 | 阿拉灯

Run in terminal莫名奇妙软件无法在QtCreator中运行或者调试&#xff0c;main函数都无法进入&#xff0c;QtCreator中一运行就崩溃&#xff0c;并跳到汇编界面&#xff0c;这多半和代码没什么关系&#xff0c;我这里是将项目->运行中的“Run in terminal”去掉勾选&#xff…

css3-13 如何改变文本框的轮廓颜色

css3-13 如何改变文本框的轮廓颜色 一、总结 一句话总结&#xff1a;outline使用和border很像&#xff0c;几乎一模一样&#xff0c;多了一个offset属性 1、轮廓outline如何使用&#xff1f; 使用和border很像&#xff0c;几乎一模一样&#xff0c;多了一个offset属性 18 …

ios添加设备真机测试,以及Undefined symbols for architecture x86_64:''错误

问题今天坑了好久&#xff0c;然后找了各种资料 添加设备这个直接去开发者中心添加一个设备进去就好&#xff0c;具体流程百度&#xff0c;第二个问题是属于路径不对或者是静态库没有添加成功&#xff0c;项目可以看到&#xff0c;到时路径找不到&#xff0c;你把静态库拖到桌面…

Kinect2.0获取数据

最近事情真是多&#xff0c;今天抽空研究一下Kinec2.0的数据获取&#xff01; 系统要求 https://developer.microsoft.com/en-us/windows/kinect/hardware-setup 系统环境 联想Y430P&#xff0c;Windows10 首先安装了Kinect for Windows SDK &#xff08;KinectSDK-v2.0_1409-S…

linux超级工具,linux运维超级工具--sysdig

sysdig 是一个超级系统工具,它可以用来捕获系统状态信息&#xff0c;在运维工作中sysdig能很方便的排查异常、定位故障&#xff0c;它还能保存数据进行分析&#xff0c;并且提供强大的命令接口。在了解sysdig强大之处之前,首先得安装sysdig&#xff0c;我这里是环境是centos6.7…

Asp.net mvc 知多少(一)

本系列主要翻译自《ASP.NET MVC Interview Questions and Answers 》- By Shailendra Chauhan&#xff0c;想看英文原版的可访问http://www.dotnettricks.com/free-ebooks自行下载。该书主要分为两部分&#xff0c;ASP.NET MVC 5、ASP.NET WEB API2。本书最大的特点是以面试问答…

stm32h7能跑linux,STM32H7榨干了Cortex-M7的最后一滴血

原标题&#xff1a;STM32H7榨干了Cortex-M7的最后一滴血有个非常重磅的消息ST给自己的STM32家族又新增了一条新的产品线—— H7H 代表的是High Pefrmance之意 (此为笔者臆测)7 则表示这是基于ARM Cortex-M7架构修改而来熟悉的工程师可能会问&#xff0c;不是已经有基于M7架构的…

通过PowerShell进行网络分析

好久没有写文章&#xff0c;因为确实工作也比较忙。今天周末&#xff0c;稍微有些时间&#xff0c;在解决一个问题时&#xff0c;用到了一点抓取和处理网络数据的小技巧&#xff0c;摘录分享如下。问题描述我有一个需求&#xff0c;就是要研究某个网页加载过程中具体发起了多少…

c语言不规则窗口,C语言不规则数组和指针

不规则数组是每一行的列数不一样的二维数组&#xff0c;其原理如下图所示&#xff0c;图中的数组有3行&#xff0c;每行有不同的列数。在了解如何创建不规则数组之前&#xff0c;让我们先看一下用复合字面量创建的二维数组。复合字面量是一种C构造&#xff0c;前面看起来像类型…

php spl_autoload_register() 函数

spl_autoload_register()的用法&#xff1a; 其中$this表示当前类&#xff0c;autoload()是我注册的自动加载函数&#xff0c;当然这个只是一个函数名&#xff0c;只要不与php的关键字重复&#xff0c;符合一般函数名的命名规范即可。 使用自动加载之后&#xff0c;当我们在一个…

C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质...

C语言中递归什么时候能够省略return引发的思考&#xff1a;通过内联汇编解读C语言函数return的本质 事情的经过是这种&#xff0c;博主在用C写一个简单的业务时使用递归&#xff0c;因为粗心而忘了写return。结果发现返回的结果依旧是正确的。经过半小时的反汇编调试。证明了我…

C# 为什么说CM+Fody+HC是WPF开发的最强组合?

01—名词解析CM&#xff1a;Caliburn.Micro(简称CM)一经推出便备受推崇&#xff0c;作为一款MVVM开发模式的经典框架&#xff0c;越来越多的受到wpf开发者的青睐.我们看一下官方的描述&#xff1a;Caliburn是一个为Xaml平台设计的小型但功能强大的框架。Micro实现了各种UI模式&…

c语言逻辑运算符两侧运算对象,逻辑运算符两侧运算对象的数据类型是什么?...

逻辑运算符两侧运算对象的数据类型&#xff1a;可以是任何合法的类型数据&#xff1b;因为逻辑运算符两边的运算对象&#xff0c;最终都被转换成bool值(逻辑值)操作。0、null转换为false&#xff0c;而所有非零、非false、非null值转换为true&#xff1b;然后进行运算。逻辑运算…

创建相似对象,就交给『工厂模式』吧

源码&#xff1a; 源代码C# 系列导航&#xff1a; 目录 定义&#xff08;Factory Pattern&#xff09;&#xff1a; 用来创建目标对象的类&#xff0c;将相似对象的创建工作统一到一个类来完成。 一、简单工厂模式&#xff1a; 代码&#xff1a; /// <summary>/// 产品枚…