分析Guava并发工具类Futures

/ 技术 / 无站内评论 / 250浏览

前言

为什么要分析这个东西呢,其实guava从开始开始工作就用了,带给我们开发的效率提升不是一点半点,java很多工具类也说借鉴的guava,首先今天分析的Futures其实是因为今天线上发现一个百思不得其解的问题,线程池中有一个队列,大概在400左右,每个任务(抓取)大概最多几分钟(重试)执行完毕,执行完毕会通过Futures的回调函数进行继续处理,但是这时候就出现了很奇怪的问题,线程池在一个任务执行成功后,延迟了一个小时左右才回调!刚开始特别不理解,还以为代码异常了,各种找为什么不执行回调函数,各种找不到bug,一个小时候,奇迹出现了,400条任务像发了疯一样同时调用回调函数....

过程

代码:

Futures.addCallback(futureTask, callback);

Futures.addCallback(futureTask,callback,executorService);

以上是Futures在并发编程中常用回调的两个方法,而让我们造成回调延迟已很严重的是第二个方法,也就是多了executorService对象。

首先我们来看下无executorService对象的源代码。

 public static <V> void addCallback(
     ListenableFuture<V> future, FutureCallback<? super V> callback) {
   addCallback(future, callback, directExecutor());
 }

重点看方法directExecutor(),这里直接引用了一个静态Executor枚举。方法注释也很清楚的描述了方法作用

简单来说,就是在任务完成时,立即在当前线程上调用回调方法!

先看看Futures.addCallback(futureTask, callback);方法描述

如果回调很慢或很重,请考虑#addCallback(ListenableFuture, FutureCallback, Executor)

为什么回调很慢要考虑带Executor对象的呢?

Executor就是用来执行回调函数的对象。

而Futures.addCallback(futureTask,callback,executorService)方法描述上又有一番建议。

当回调快速且轻量级时,请考虑#addCallback(ListenableFuture, FutureCallback);

首先解释下什么是所谓的轻量级,什么是所谓的重!

轻量级就是执行回调方法FutureCallback时,会不会耗时很久,会不会占用线程池任务时间过长。

基于以上原因,就可以来讨论开发时怎么根据业务场景使用哪个方法。

现在就可以说说为什么我的回调很慢了。

1.我使用了Futures.addCallback(futureTask,callback,executorService);方法,同时传入的executorService对象是和futureTask公用一个。

然后导致线程池任务和回调公用一个队列!

在并发高的时候,当一个任务执行完毕之后,回调函数被当作一个“任务”一样丢到队列中等待调用。

也就是说,当第一个任务执行完毕之后,程序将回调函数丢到任务队列尾部。

因此当所有任务执行完毕之后,再统一执行回调方法!!!!所以就是延迟回调的根本原因。

总结

Futures.addCallback(futureTask, callback);

1.回调函数执行的方法耗时不长

(如果耗时很长,就会阻塞其他任务,导致其他任务等待时间过长,不着急的处理流程可以放到回调函数中进行处理。)

2.要求任务执行完毕立马回调

Futures.addCallback(futureTask,callback,executorService);

1.回调函数执行的方法非常消耗资源。

其实这里的executorService,可以单独定义一个队列供回调方法使用,这样既可以快速回调也不会影响到线程池任务队列。


召唤蕾姆
琼ICP备18000156号

鄂公网安备 42011502000211号