Android中的线程池-应用场景分析笔记

3,139 阅读4分钟

我吐了,老忘点边边脚脚 好记性不如烂笔头!

线程池的作用

  • 1.重用线程池中的线程,减少线程的创建和销毁带来的开销
  • 2.有效的控制线程的最大并发数,避免大量线程之间因为相互抢占系统资源而导致的阻塞现象。
  • 3.提供简单的管理,定时执行,指定间隔循环执行,线程资源常驻及释放

线程池的配置

Android中线程池的概念来源于java中的Executor,具体实现为 ThreadPoolExecutor。可以通过它的构造参数来创建不同类型的线程池。

构造参数详细

对参数作用充分理解后,方便后面对默认提供的四种线程池的应用场景的分析

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

  • 1.corePoolSize 核心线程最大数量

情况1:allowCoreThreadTimeout = flase(默认),核心线程会伴随线程池的整个生命周期一直存活,即使处于闲置

情况2:allowCoreThreadTimeout = true,闲置的核心线程在等待新任务时可触发超时策略,时间间隔为keepAliveTime

  • 2.maximumPoolSize 线程池能容纳的最大线程数

当活动线程数量达到这个值以后,后续的新任务将会被阻塞(进度阻塞队列,等待绑定执行)

  • 3.keepAliveTime 非核心线程闲置的超时时长(在1中情况2时也会作用于核心线程)

闲置线程等待任务超过这个时长,将会被回收。

  • 4.unit keepAliveTime 的单位

可以使用毫秒,秒,分钟

  • 5.workQueue

线程池的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。

  • 6.threadFactory

线程工厂,为线程池提供创建新线程的功能的接口。

  • RejectedExecutionHandler 拒绝执行的回掉

当线程池无法执行新任务时,任务队列已满或是无法成功执行任务。

合理配置参数

参考AsyncTask,但是这并不是一个固定指标。

AsyncTask的线程池配置如下:

核心线程数=CPU核心数+1

线程池最大线程数=CPU核心数量*2+1

核心线程无超时,非核心线程闲置超时时间=1s

任务队列容量为128

ThreadPoolExecutor 线程创建规则

  • 1.如果线程池中的线程数量未达到核心线程的数量,则直接启动一个核心线程来执行任务。
  • 2.如果线程池中的数量>=核心线程数量,那么任务将会被插入任务队列中,排队等待执行
  • 3.如果2中无法将任务插入到任务队列中(正常情况下任务队列已满),此时如果线程总数(核心+非核心)<=线程池配置的最大线程数,则启动一个非核心线程来执行任务
  • 4.如果3中线程总数量已达到线程池配置的最大线程数量,则拒绝执行此任务,回调RejectedExecutionHandler的rejectedExecution方法。

线程池的分类应用

SDK提供了4中常用线程池,分别适应不同的应用场景。FixedThreadPool,CachedThreadPool,ScheduledThreadPool,SingleThreadExecutor

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

核心线程数量=最大线程数量,超时时间=0。所以这个线程池只有核心线程,而且核心线程不会超时。一个吞吐量固定,长期营业的线程池。

优点:响应任务的速度快,任务量与吞吐量饱和时,任务处理效率最大化。

缺点:并发能力较弱,吞吐量>任务数量时不可避免会造成资源浪费(主要是内存)。

应用场景:处理需要长期快速响应,无很高的并发效率要求。对批量任务执行无较高的时间等待要求。

  • CachedThreadPool 裸奔版本
   public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

没有核心线程,只有非核心线程 数量=Integer.MAX_VALUE。超时时间60s,无缓存任务队列(SynchronousQueue:简单理解为一个无法存储元素的队列)

优点:所有任务都会被立即分配线程执行,几乎可以理解为一个突破手。用于处理集中并发任务。在全部线程都超时后,这个线程池几乎是不占任何系统资源的(我将这里类比为主动new N个普通线程) 缺点:随着任务数量激增可能会导致系统资源匮乏,导致线程阻塞。 应用场景:处理大量耗时较少的任务

  • ScheduledThreadPool 调度版

 public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue(), threadFactory);
    }
    
       public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    

非核心线程数量Integer.MAX_VALUE,闲置线程等待超时10s。

优点:非核心线程10s就会被即时回收,释放速度快,吞吐量大于FixedThreadPool 响应速度略高于CachedThreadPool。

缺点:这是一个非极端的线程池,缺点不明显

应用场景:使用schedule()方法处理定时任务,或处理固定周期的重复任务,执行完后直到下次执行期间,尽量少的占用资源。可以参考这个配置自定义线程池,更好的适应具体场景,比如将DEFAULT_KEEPALIVE_MILLIS 设置为0s,加速资源释放。

  • SingleThreadExecutor 单实例版
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

就一个核心线程,没有非核心线程,队列使用LinkedBlockingQueue。毫无疑问这是个任务队列+单线程的顺序执行的“任务池”。

应用场景:处理需要按顺序执行的任务。


END