背景
我们对外提供了一个 sdk,收到客户的反馈,说他们监控平台监测到 sdk 内部的一个接口 A 经常有耗时,最多能达到 10 几秒。
可从我们自己的平台看,接口 A 的耗时是正常范围的,而且使用的网络库 timeout = 8s,不太可能出现耗时 10 几秒的问题。
由于客户也没有稳定的复现场景,也不排除是对方统计工具的问题,就卡住了。
进展
重新回到代码中,经过又一番的代码走查,注意到了该接口有使用 AsyncTask ,点进 AsyncTask 源码一看,突然有种玄学变科学的感觉:
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_SECONDS = 3;
public static final Executor THREAD_POOL_EXECUTOR;
...
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
了解线程池的应该都知道,
CORE_POOL_SIZE : 核心线程数。核心线程只有在 allowCoreThreadTimeOut = true 时,才会被回收,否则不会被回收掉的,即使没有任务执行,也会保持空闲状态。
MAXIMUM_POOL_SIZE : 线程池最大允许创建的线程数。当线程数量达到 corePoolSize, 且 workQueue 队列塞满后,才会继续创建线程;当创建的线程达到 MAXIMUM_POOL_SIZE 时,新来的任务会执行拒绝策略。
KEEP_ALIVE_SECONDS : 表示非核心线程在没有任务执行时,最多能保持多少时间会被回收。
SynchronousQueue
直接在网上找了一段容易理解的定义:
“ SynchronousQueue 是一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候就会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。”
结论
由于 CORE_POOL_SIZE = 1, 且阻塞队列为 SynchronousQueue,所以 AsyncTask 执行的任务为串行执行。而其 THREAD_POOL_EXECUTOR 线程池又是静态的,所以不同 AsyncTask 对象会共用该线程池。所以我们应当避免使用 AsyncTask,尤其是在 sdk 开发中。
经过全局查找,发现项目内还有一个鉴权接口 B 也使用了 AsyncTask,所以该事故原因为,在网络环境较差时,接口 B 请求服务,会阻塞接口 A 请求, 导致客户统计耗时增加,最大耗时 2 * timeout。
方案
规范代码,使用统一的线程池。ThreadPoolSchedulers 就不细贴了,就是创建了一个 Executor
asyncTask.executeOnExecutor(ThreadPoolSchedulers.io());
文章完,麻烦动动你们发财的小手,点点赞好吗?