Android线程池的使用

一、线程与线程池,为什么要使用线程池

1、Android中的线程

在Android中有主线程和子线程的区分。主线程又称为UI线程,主要是处理一些和界面相关的事情,而子线程主要是用于处理一些耗时比较大的一些任务,例如一些网络操作,IO请求等。如果在主线程中处理这些耗时的任务,则有可能会出现ANR现象(App直接卡死)。

2、Android中的线程池

线程池,从名字的表明含义上我们知道线程池就是包含线程的一个池子,它起到新建线程、管理线程、调度线程等作用。

3、为什么要使用线程池

既然Android中已经有了线程的概念,那么为什么需要使用线程池呢?我们从两个方面给出使用线程池的原因。

  • 首先线程的新建和销毁都是存在性能上的消耗的,如果一个时间段有大量的网络请求,那么就需要多个线程的创建与销毁,性能上的损耗可想而知。
  • 其次多个线程的存在也会占用CPU的执行时间段。我们知道如果只有一个CPU存在,那么线程的执行都是CPU轮流将执行时间分配给每一个线程。如果同时有多个子线程存在,那么相应的分配到主线程的CPU执行时间也会变少,这样App很大可能会出现卡顿现象。 鉴于上述两方面的原因,我们引进来了线程池这样一个概念,线程的创建、调度、销毁等都是由线程池来管理,这样就可以做到线程的重用,不必每次都新建一个线程,减少了线程创建和销毁的性能损耗。同时线程池会限制创建线程的个数,让App中的线程个数保持在一个可控的范围,这样也可以控制多个线程抢占主线程的资源。

二、Android中的线程池的介绍

1、线程池的构造

在Android中线程池就是ThreadPoolExecutor对象。我们先来看一下ThreadPoolExecutor的构造函数。

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

我们分别说一下当前的几个参数的含义: 第一个参数corePoolSize为核心线程数,也就是说线程池中至少有这么多的线程,即使存在的这些线程没有执行任务。但是有一个例外就是,如果在线程池中设置了allowCoreThreadTimeOut为true,那么在超时时间(keepAliveTime)到达后核心线程也会被销毁。 第二个参数maximumPoolSize为线程池中的最大线程数。当活动线程数达到这个数后,后续添加的新任务会被阻塞。 第三个参数keepAliveTime为线程的保活时间,就是说如果线程池中有多于核心线程数的线程,那么在线程没有任务的那一刻起开始计时,如果超过了keepAliveTime,还没有新的任务过来,则该线程就要被销毁。同时如果设置了allowCoreThreadTimeOut为true,该时间也就是上面第一条所说的超时时间。 第四个参数unit为第三个参数的计时单位,有毫秒、秒等。 第五个参数workQueue为线程池中的任务队列,该队列持有由execute方法传递过来的Runnable对象(Runnable对象就是一个任务)。这个任务队列的类型是BlockQueue类型,也就是阻塞队列,当队列的任务数为0时,取任务的操作会被阻塞;当队列的任务数满了(活动线程达到了最大线程数),添加操作就会阻塞。 第六个参数threadFactory为线程工厂,当线程池需要创建一个新线程时,使用线程工厂来给线程池提供一个线程。 第七个参数handler为拒绝策略,当线程池使用有界队列时(也就是第五个参数),如果队列满了,任务添加到线程池的时候的一个拒绝策略。

2、线程池的分类

I、FixedThreadPool

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

可以看到FixedThreadPool的构建调用了ThreadPoolExecutor的构造函数。从上面的调用中可以看出FixedThreadPool的几个特点:

  • 它的核心线程数和最大线程数是一样的,这意味着当线程处于空闲状态时,线程都不会被销毁(因为最大线程数和核心线程数一样)。但是其设置的线程保活时间keepAliveTime为0,这意味着当线程池设置了allowCoreThreadTimeOut为true时,处于空闲状态的线程马上就会被销毁。 当所有的线程都处于活动状态时,新来的任务会处于等待状态,并在有空闲线程时执行。

II、CacheThreadPool

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

可以看到CacheThreadPool的构建调用了ThreadPoolExecutor的构造函数。从上面的调用中可以看出CacheThreadPool的几个特点:

  • 它的核心线程数为0,最大线程数为Integer.MAX_VALUE(可以看做无限制了)。线程保活时间keepAliveTime为60秒。这意味着当线程池中的所有线程都处于活动状态时,会新建一个线程来执行该任务;当线程池中有空闲线程时(虽然没有核心线程,但是线程有60秒的保活时间,并不是空闲下来就马上被销毁)就使用空闲线程。 这里需要注意的是任务队列SynchronousQueue同步阻塞队列,也就是说队列没有任何容量,只有在有线程可以执行任务时才可以向其中添加任务。从上面CacheThreadPool的特点可以看出,保证是可以有线程随时执行任务的(要不就new一个,要不就是空闲线程),所以这里并不会出现阻塞现象。 关于BlockQueue的具体介绍,大家可以看一下Java多线程-工具篇-BlockQueue这篇文章。 所以针对CacheThreadPool的特性,当我们需要执行大量任务时,但是这些任务耗时又比较少时,可以考虑使用CacheThreadPool。

III、ScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}	

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
复制代码

可以看到ScheduledThreadPoolExecutor的构建调用了ThreadPoolExecutor的构造函数。从上面的调用中可以看出ScheduledThreadPoolExecutor的几个特点:

  • 它的核心线程数是固定的,而最大线程数为Integer.MAX_VALUE(可以看做无限制了)。线程保活时间keepAliveTime为0。这意味着当线程池中超过核心线程数的线程空闲下来后马上就会被销毁。 ScheduledThreadPool一般用来执行一些定时任务或者是一些固定周期的重复任务。 可以通过executor.schedule(有几个不同的重载方法)分别执行不同的延时、定时、周期重复任务。

IV、SingleThreadExecutor

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

可以看到SingleThreadExecutor的构建调用了ThreadPoolExecutor的构造函数。从上面的调用中可以看出SingleThreadExecutor的几个特点:

  • 它的核心线程数是1,最大线程数也是1,线程保活时间keepAliveTime为0,这意味着线程池中最多只能允许一个线程存在,并且该线程不管是否是运行还是空闲的,都不会被销毁。 SingleThreadExecutor保证了所有的任务都在一个线程中按照顺序执行。

猜你喜欢

转载自juejin.im/post/5d7318915188256f3b09bb50