Android进阶——线程治理与线程池

1. 为什么需要线程池

程序中不管是网络请求、文件IO、数据库操作等其他耗时操作都需要异步进行,而由于线程创建和销毁都需要一定的开销,如果每次执行异步任务都重新创建一个线程,并在完成任务后直接进行销毁,这会消耗大量资源。JAVA在1.5中提供了Executor,通过将任务的创建和执行解耦, 如下图所示
在这里插入图片描述
即通过Runnable和Callable接口实现延时启动/异步启动任务并通过Future返回执行结果
整个Executor最核心的就是ThreadPoolExecutor,我们首先来看看他的原理;

2. ThreadPoolExecutor

2.1 ThreadPoolExecutor的构造

ThreadPoolExecutor一共有四个构造方法,如下所示:

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

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

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

可以看到最终都是调用了最后一个构造,其参数含义如下:

corePoolSize :核心线程数,默认情况下新建的线程池是空的,当有新任务进来时,会判断当前线程数量是否小于核心线程数,小于则创建新线程执行该任务,反之不会创建,另外如果在创建线程池的同时调用了prestartAllcoreThread()则会在初始化完成后新建全部核心线程并等待任务;
maximumPoolSize : 最大线程数,一个新任务进来时,如果当前任务队列已满但线程数还没有达到最大线程数,则创建新线程;
keepAliveTime : 非核心线程闲置的超时回收时间,默认是1000ms,当非核心线程闲置超过这个时间后将会被回收,在一些任务数量多、任务平均耗时短的业务场景下可以适当调大此值以提高线程池效率,如果设置了allowCoreThreadTimeout(true)的话,则此超时时间限制也会用于核心线程;
unit: keepAliveTime参数的单位,可以是天、时、分、秒、毫秒;
workQueue: 任务队列,是一个阻塞队列;
threadFactory: 线程工厂,可以用它来为线程池中的线程设置名称,用的场景不多,一般默认不传该参数即可;
handler: 饱和策略,当线程池中任务队列已满且当前线程数已达到最大线程,如果此时有新任务进入,会触发饱和策略,默认为AbortPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。此外还有3种策略,它们分别如下:
①CallerRunsPolicy: 用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
②DiscardPolicy: 不能执行的任务,并将该任务删除。
③DiscardOldestPolicy: 丢弃队列最近的任务,并执行当前的任务。

2.2 ThreadPoolExecutor中新任务处理流程

① 当一个新任务进入时,首先会判断当前线程数是否达到核心线程数,如果没达到则创建一个新的核心线程执行该任务;
② 如果当前未达到核心线程数,则创建核心线程执行任务,如已达到核心线程数,则检查当前任务队列是否已满;
③ 如果当前任务队列未满,则将新任务加到任务队列中,如任务队列已满,则判断当前线程数是否达到最大线程数;
④ 如果已经达到最大线程数,则触发饱和机制,如果未达到最大线程数,则创建一个新的线程执行新任务;

在这里插入图片描述

4.3 常用线程池
4.3.1 FixedThreadPool
// 构造方法
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

通过构造方法可以很容易看出,FixedThreadPool特点:①核心线程数 = 最大线程数 = nThreads(构造传入);②keepAliveTime为0;③任务队列类型为一个无界阻塞队列
结合前面对于ThreadPoolExecutor构造参数的解释,可以得出FixedThreadPool处理任务的过程如下:
①如果当前运行的线程数少于corePoolSize, 会立刻创建新线程执行任务。
②当线程数到达corePoolSize后,将任务加入到LinkedBlockingQueue中。
③当线程执行完任务后,会循环从LinkedBlockingQueue中获取任务来执行。

FixedThreadPool使用了LinkedBlockingQueue, 也就是无界队列(队列最大可容纳Integer.MAX_VALUE), 因此理论上任务可以无限添加,直到内存溢出;

4.3.2 CacheThreadPool
// 构造方法
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

通过构造方法可以看出:CacheThreadPool特点为:①核心线程数为0;②最大线程数为MAX_VALUE,可以理解为不设限;③非核心线程超过60s无任务执行会被回收;④任务队列类型为SynchronousQueue;
SynchronousQueue是一种不存储任务的阻塞队列,他的插入和删除是互斥的,也就是插入新任务的同时不允许执行移除任务操作。所以当一个新任务进入时:CacheThreadPool的处理流程应当如下:由于核心线程数始终为0,且任务队列为无界队列,所以新任务会通过SynchronQueue().offer方法加进任务队列中,接着检查当前是否有空闲线程,如果有则直接将新任务交给空闲线程去执行,如果没有则创建一个新线程并执行该任务;当有一个线程空闲下来时,会通过SynchronQueue().poll方法取任务去执行,如果超过60s都无法取到任务,则销毁当前线程;CachedThreadPool 比较适于大量的需要立即处理并且耗时较少的任务。

4.3.3 SingleThreadExecutor
// 构造方法
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

通过构造方法可以看出,SingleThreadExecutor:①核心线程数=最大线程数=1的线程池;② 线程超时回收时间为0(即无任务执行时线程立刻被回收);③消息队列和FixedThreadPool一样,为无界链式消息队列;
SingleThreadExecutor执行任务流程为,一个新任务进来后,首先判断当前是否存在线程,如果不存在则创建一个线程(当然,最多也只能存在一个)执行新任务,如果当前已有线程(如果有线程存在,则该线程一定正在执行任务,因为从构造方法中我们知道,线程执行完任务会立即被回收)则将新任务加到队列中,由于队列本身是有序的,所以SingleThreadExecutor实际上保证了所有任务按照进入顺序依次执行。

4.3.4 ScheduledThreadPool
// 构造方法
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

从构造方法可以看出ScheduledThreadPool实际上是构造了ScheduledThreadPoolExecutor(),这点是与前面说到的三个线程池都不同的,前三个都是构造ThreadPoolExecutor(),而实际上ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,主要封装了延迟,定时相关的功能,其构造方法如下:

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

可以看到实际上是通过super构造了ThreadPoolExecutor,结合这两层套娃,我们可以得出ScheduledThreadPool的特点如下:
① 最大线程数为Interger.MAX_VALUE,即溢出前不设限;②超时时间为0;③消息队列为DelayWorkQueue();

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值