Android - 线程池介绍及使用

2018-12-25  本文已影响0人  xlq

使用背景:

在我们的应用中,涉及到多线程,肯定会联想到线程池。例如:一个List列表中每个Item项都需要下载一张图片来显示,如果使用子线程,每一项都需要新起一个子线程来下载。这样做的后果是,不断的创建线程,用完之后便销毁,这样造成频繁的GC回收,严重影响性能。并且,线程太多不便管理,如果用户将列表滑出屏幕,此时并不需要继续下载,要想停止滑出屏幕的线程会显得格外麻烦。这种情况下,我们会首选线程池。

线程池介绍:

android中为我们提供了ThreadPoolExecutor的类来创建线程池。其构造方法有多种,下面介绍一种参数最多的构造方法。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        ...
    }

依照惯例逐个介绍一下,便于自己的记忆。
corePoolSize:核心线程数
maximumPoolSize:线程池中的线程总数(核心程序+非核心线程);
keepAliveTime:非核心线程空闲时的存活时间。当allowCoreThreadTimeOut属性设为true时,该属性也可用于核心线程;
unit:keepAliveTime的时间单位;
workQueue:阻塞队列。当核心线程以满,剩下的任务放置在阻塞队列中,便于有空闲线程去处理;
threadFactory:用于设置线程名称,创建线程,一般不用设置,使用默认值。

线程池执行策略:

  1. 当线程数为达到核心线程数,此时创建核心线程来执行剩下的任务;
  2. 当已达到核心线程数,此时将剩下的任务放入阻塞队列,等待核心线程空闲之后,再从队列中获取执行。
  3. 当队列中的任务已满,则创建非核心线程来处理剩下的任务,此时的任务不经过阻塞队列。
  4. 当阻塞队列已满,且线程池中的核心与非核心线程总数也已经达到了最大线程数。此时执行饱和策略,抛出RejectedExecutionException异常

几种常见的线程池:

FixThreadPool,构造方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

特征:
1.核心线程数与线程池总数一样,说明没有非核心线程。
2.超时时间为0,表示即使闲置,线程也不会被回收。

  1. 阻塞队列的队列长度为空,即Integer.MAX_VALUE(2的31次方-1),可以无限往队列中放置任务

使用场景:适用于任务量比较固定但耗时长的任务。

SingleThreadPool,构造方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>());
    }

特点: 线程池中只有一个线程,无超时销毁时间,阻塞队列长度为无限大
SingleThreadExecutor的意义在于统一外界所有任务到一个线程,这使得这些任务之间不需要处理线程同步的问题.

使用场景:适用于执行定时任务和具体固定周期的重复任务。

CacheThreadPool,构造方法如下:
public static ExecutorService newCacheThreadPool(int nThreads) {
        return  new ThreadPoolExecutor(
                0,Integer.MAX_VALUE,
                60L,TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>()
        );
    }

特点:没有核心线程,非核心线程数量可以无限大,60s超时销毁。阻塞队列使用SynchronousQueue,该队列可以理解为无法储存的队列,只有在可以取出的情况下,才会向其内添加任务.

使用场景:比较适合任务量大但耗时少的任务。

ScheduledThreadPool,构造方法如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSzie) {
    return new ScheduledThreadPoolExecutor(corePoolSzie);
}
//核心线程数是固定的,非核心线程无限大,并且非核心线程数有10s的空闲存活时间
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, 
              Integer.MAX_VALUE, 
              DEFAULT_KEEPALIVE_MILLIS, 
              MILLISECONDS, 
              new DelayedWorkQueue());
}

特点:它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收.
ScheduThreadPool这类线程池主要用于执行定时任务和具有固定周期的重复任务.
而DelayedWorkQueue这个队列就是包装过的DelayedQueue,这个类的特点是在存入时会有一个Delay对象一起存入,代表需要过多少时间才能取出,相当于一个延时队列。
使用场景:适用于执行定时任务和具体固定周期的重复任务。

OK,暂时到这里,有空会继续更新线程池的API使用及意义。

上一篇 下一篇

猜你喜欢

热点阅读