从概念到实现,再从应用到原理。关于线程池,你所应该知道的一切。
线程池简介
在操作系统中,线程时操作系统调度的最小单元。同时线程又是一种受限的系统资源,什么意思呢?就是说线程不可能无限制的产生,并且线程的创建和销毁的过程都会产生相应的开销。
当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程。因此线程不可能做到绝对的并行,除非线程数小于等于CPU的核心数,一般这是不太可能的。
如果我们在一个进程中,频繁地创建和销毁线程,显然不是一个高效的做法。线程池就是为了解决这一问题,应运而生。
线程池简单来说,就是一个缓存了一定数量线程的区域。
它的优点可以大致归纳为以下三点。
重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
能有效地控制线程的最大并发数,避免大量的线程因为互相抢占资源而导致的阻塞现象。
能够对线程进行简单的管理,并提供定时执行以及指定时间间隔循环执行等功能。
线程池的实现
Android中的线程池的概念,来源于Java中的Executor。Executor是一个接口,它的真正实现类是ThreadPoolExecutor,ThreadPoolExecutor的构造方法提供了一系列的参数来配置线程池。
1 | public ThreadPoolExecutor (int corePoolSize, |
接下来,对这些参数逐一做简单的解释。
核心线程数(corePoolSize)
默认情况下,核心线程会在线程池中一直存活,即便它们属于闲置状态。
如果我们改变ThreadPoolExecutor的allowCoreThreadTimeOut属性,将其设置为true,那么核心线程在闲置时会有超时策略。当闲置核心线程的等待新任务时间超过某个时长后,核心线程就会被终止。这个时长由keepAliveTime指定。
最大线程数(maximumPoolSize)
当线程池的活动线程数达到最大线程数后,后续新的任务将会被阻塞。
超时时长(keepAliveTime)
非核心线程如果闲置时长超过这个超时时长,非核心线程就会被回收。
上面提到过,如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,核心线程同样也会被这个超时时长支配。
时间单位(unit)
这是超时时长参数的时间单位,为一个枚举。常见的有TimeUnit.MILLISECONDS、TimeUnit.SECONDS、TimeUnit.MINUTES等。
任务队列(workQueue)
通过线程池的execute方法提交的Runnable对象会储存在任务队列这个参数中。
线程工厂(threadFactory)
线程工厂为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个实现方法:Thread newThread(Runnable r)。
线程池的运行
ThreadPoolExecutor在执行任务时会大致遵循以下的规则。
如果线程池中的线程数量没有达到核心线程的数量,会直接创建一个核心线程来执行任务。
如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
如果任务队列已满,将无法再继续插入。这时如果线程数量如果没有达到线程池规定的最大值,则立刻启动一个非核心线程来执行任务。
如果任务队列已满,而且线程数量已达到线程池规定的最大值,那么就拒绝执行此任务。ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
线程池的使用
线程池的用法如下。
创建线程池
创建时,通过配置线程池的参数,从而实现自己所需内存。下面以AsyncTask中的线程池为例。
1 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcess(); |
我们可以很明显的看出线程池的配置规格。
核心线程数为 CPU核心数+1。
最大线程数为 CPU核心数的2倍+1。
超时时长为 1秒。
任务队列的容量为 128。
提交任务
通过execute()方法,传入Runnable对象,向线程池提交任务。
1 | threadPool.execute(new Runnable() { |
关闭线程池
1 | threadPool.shutdown(); |
此时,会遍历线程池中的所有工作线程,然后逐个调用线程的interrupt()方法中断线程。
shutdown(): 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程。
shutdownNow(): 设置线程池的状态为STOP,然后中断所有没有正在执行任务或暂停任务的线程。并返回等待任务的列表。
一般多以shutdown()关闭线程池。如果任务不一定要执行完,则以shutdownNow()关闭线程池。
常见线程池
Android中有四种最常见的线程池,它们都是通过配置好核心参数,来实现自己的功能特性。
定长线程池
定长线程池通过Executors.newFixedThreadPool()创建。
它的特性如下。
- 线程数量固定。
- 只有核心线程且无超时机制(不会被回收)。
- 任务队列无大小限制。
1 | // 创建定长线程池并设置核心线程数量为5 |
它的特性意味着它能够更快速的响应外界的请求。我们可以通过它控制线程的最大并发数。
可缓存线程池
可缓存线程池通过Executors.newCachedThreadPool()创建。
它的特性如下。
- 只有非核心线程。
- 非核心线程数量无限制。
- 超时时长为60秒。
1 | // 创建可缓存线程池 |
它的特性意味着任何任务到来都会立刻执行,不需要等待。比较适合执行大量的耗时少的任务。
定时线程池
定时线程池通过Executors.newScheduledThreadPool()创建。
它的特性如下。
- 核心线程数量固定。
- 非核心线程数量无限制。
- 超时时长为0,非核心线程闲置马上回收。
1 | // 创建定时线程池并设置核心线程数量为5 |
它主要用于执行定时任务和具有固定周期的任务。
单线程化线程池
单线程化线程池通过Executors.newSingleThreadExecutor()创建。
它的特性如下。
- 只有一个核心线程。
- 确保所有任务都在一个线程中按顺序执行。
1 | // 创建单线程化线程池 |
它能够统一所有的外界任务到一个线程中,使这些任务之间不需要处理线程同步的问题。
总结
好了,认真读完本文后,你已经对线程池的概念和用法了如指掌。接下来,就是根据项目需求,灵活地运用它们吧!