android线程池

245 阅读4分钟

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。

ThreadPoolExecutor

基本介绍

Android的线程池分为4类,这4类线程池都是通过配置ThreadPoolExecutor的构造参数来实现的,因此这里首先介绍ThreadPoolExecutor
对于ThreadPoolExecutor我们可以将其理解为一个流水线,该流水线的产品是我们提交的任务,任务会放在任务队列中,处理任务的是线程池,更具体一点是线程池中的核心线程或者非核心线程
首先来看ThreadPoolExecutor的构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

这几个参数其实也很好记忆

  • corePoolSize :核心线程的个数
  • maximumPoolSize:线程池最大的线程数
  • keepAliveTime: 非核心线程等待的最长时间,如果超过这个时间非核心线程会回收,如果线程池设置了allowCoreThreadTimeOut为true,那么keepAliveTime对所有的线程生效
  • unit:keepAliveTime的单位,属于枚举类型
  • workQueue 任务列表
  • threadFactory:为线程池添加新的线程
public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}
  • handler :android关于这里使用handler的原因如下
* @param handler the handler to use when execution is blocked
*        because the thread bounds and queue capacities are reached

也就是说,如果线程执行失败或者任务队列已满,就是通过handler去通知任务执行失败

使用流程

当任务涌入时,核心线程首先被创建,任务与核心线程之间是一一对应的关系,直到核心线程已满,此时任务会放入任务队列,当任务队列容不下时会开始创建非核心线程直到非核心线程也已满,如果此时任务队列也已满,那么任务将终止接收并通过handler通知出去

线程池分类

android按照生成ThreadPoolExecutor时所给具体参数值的不同将线程池分为4类,这4类通过工具类Executors来生成

FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

通过对参数的分析可以知道FixedThreadPool核心线程数等于最大线程数,也就是说FixedThreadPool只有核心线程,对于任务队列则没有限制

CachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

与FixedThreadPool相对应的是CachedThreadPool没有核心线程,但是其普通线程可以到Int的最大值,同时需要注意的是这里任务队列采用了SynchronousQueue这个数据结构,关于这个数据结构原文有这样的注释

/**
 * A {@linkplain BlockingQueue blocking queue} in which each insert
 * operation must wait for a corresponding remove operation by another
 * thread, and vice versa.  A synchronous queue does not have any
 * internal capacity,...
 */

这里只截取了前面的一小部分,大意是该队列的每个入队操作必然伴随着另一个线程对其出队操作,这个队列没有任何容量。
也就是说我们往该任务队列中插入的每个任务都会被及时取出并且处理

ScheduledThreadPool

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;

这里的DelayedWorkQueue是一个优先队列,也就是堆的结构,延迟时间短的任务放在上面,个人理解之所以用优先队列是因为为了保证任务定时执行的准确性,如果当前延迟已到的任务放在其他位置那么任务出队就需要额外时间,而这个时间是很难计算是多少的,如果放在堆顶,那么复杂度为o(1),几乎可以忽略

SingleThreadPool

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

只有一个核心线程,所有任务都在一个线程中完成,没有线程同步的问题

总结

至此,android线程相关大致走了一遍,从最开始线程间的以Handler为开头的消息传递机制开始,到以Handler作为底层的AsyncTask,HandlerThread,IntentService,再到对线程管理的线程池