为什么要分析这个东西呢,其实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,可以单独定义一个队列供回调方法使用,这样既可以快速回调也不会影响到线程池任务队列。