Android修炼系列:一个由 AsyncTask 引起的线上问题

背景

我们对外提供了一个 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());

文章完,麻烦动动你们发财的小手,点点赞好吗?
Android修炼系列:一个由 AsyncTask 引起的线上问题

上一篇:进程池与线程池


下一篇:ZooKeeper : Curator框架之分布式屏障DistributedBarrier