使用线程池的好处可以归纳为3点:
- 重用线程池中的线程, 避免因为线程的创建和销毁所带来的性能开销.
- 有效控制线程池中的最大并发数,避免大量线程之间因为相互抢占系统资源而导致的阻塞现象.
- 能够对线程进行简单的管理,可提供定时执行和按照指定时间间隔循环执行等功能.
ThreadPoolExecutor是Executors类的底层实现。android中线程池的概念来源于java中的Executor,线程池真正的实现类是ThreadPoolExecutor,它间接实现了Executor接口。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数配置实现不同功能特性的线程池,android中的Executors类提供了4个工厂方法用于创建4种不同特性的线程池给开发者用.
android中的线程池都是直接或是间接通过配置ThreadPoolExecutor来实现的
public class Executors {
...
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
...
}
ThreadPoolExecutor 的配置参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize: 线程池的核心线程数,默认情况下, 核心线程会在线程池中一直存活, 即使处于闲置状态. 但如果将allowCoreThreadTimeOut设置为true的话, 那么核心线程也会有超时机制, 在keepAliveTime设置的时间过后, 核心线程也会被终止.
maximumPoolSize: 最大的线程数, 包括核心线程, 也包括非核心线程, 在线程数达到这个值后,新来的任务将会被阻塞.
keepAliveTime: 超时的时间, 闲置的非核心线程超过这个时长,讲会被销毁回收, 当allowCoreThreadTimeOut为true时,这个值也作用于核心线程.
unit:超时时间的时间单位.
workQueue:线程池的任务队列, 通过execute方法提交的runnable对象会存储在这个队列中.
threadFactory: 线程工厂, 为线程池提供创建新线程的功能.
handler: 任务无法执行时,回调handler的rejectedExecution方法来通知调用者.
ThreadPoolExecutor的执行过程
- 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。(什么意思?如果当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)
- 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
- 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
用currentSize表示线程池中当前线程数量,将上述过程可以表示如下
1. 当currentSize < corePoolSize时,直接启动一个核心线程并执行任务。
2. 当currentSize>=corePoolSize、并且workQueue未满时,添加进来的任务会被安排到workQueue中等待执行。
3. 当workQueue已满,但是currentSize < maximumPoolSize时,会立即开启一个非核心线程来执行任务。
4. 当currentSize >= corePoolSize、workQueue已满、并且currentSize > maximumPoolSize时,调用handler默认抛出RejectExecutionExpection异常。
一句话概括来说就是先装满核心线程,再装满workQueue,再装满非核心线程,都装满则拒绝任务。
任务队列workQueue
在上述执行过程中有个关键的承载工具workQueue,任务队列queue的种类有三种:
- 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
- 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
- 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
Android中的4种常用的线程池
通过配置不同参数的ThreadPoolExecutor, 有4类常用的线程池, Executors类提供了4个工厂方法用于创建4种不同特性的线程池给开发者用.
1.FixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
用法:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(num);
fixedThreadPool.execute(runnable对象);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点:只有核心线程数,并且没有超时机制,因此核心线程即使闲置时,也不会被回收,因此能更快的响应外界的请求.
2.CachedThreadPool
池里的线程数量并不是固定的,理论上可以无限大,任务不需要排队,如果有空闲的线程,则复用,无则新建线程。
用法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(runnable对象);
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:没有核心线程,非核心线程数量没有限制, 超时为60秒.
适用于执行大量耗时较少的任务,当线程闲置超过60秒时就会被系统回收掉,当所有线程都被系统回收后,它几乎不占用任何系统资源.
3.ScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
用法:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
scheduledThreadPool.schedule(runnable对象, 2000, TimeUnit.MILLISECONDS);
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
特点:核心线程数是固定的,非核心线程数量没有限制, 没有超时机制.
主要用于执行定时任务和具有固定周期的重复任务.
4.SingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(runnable对象);
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点:只有一个核心线程,并没有超时机制.
意义在于统一所有的外界任务到一个线程中, 这使得在这些任务之间不需要处理线程同步的问题.
下面是一个对线程池的简单封装,管理项目中的线程。
/**
* 线程池管理
*/
public class UPThreadPoolManager {
private static UPThreadPoolManager mInstance = new UPThreadPoolManager();
public static final synchronized UPThreadPoolManager getInstance() {
if (mInstance == null) {
mInstance = new UPThreadPoolManager();
}
return mInstance;
}
private int corePoolSize; // 核心线程池的数量,同时能够执行的线程数量
private int maximumPoolSize;// 最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
private long keepAliveTime = 3;// 存活时间
private TimeUnit unit = TimeUnit.MINUTES;
private ThreadPoolExecutor executor;
private UPThreadPoolManager() {
/**
* 给corePoolSize赋值:当前设备可用处理器核心数*2 + 1,能够让cpu的效率得到最大程度执行
*/
corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
maximumPoolSize = corePoolSize; // 虽然maximumPoolSize用不到,但是需要赋值,否则报错
executor = new ThreadPoolExecutor(corePoolSize, // 当某个核心任务执行完毕,会依次从缓冲队列中取出等待任务
maximumPoolSize, // 5,先corePoolSize,然后new
// LinkedBlockingQueue<Runnable>(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
keepAliveTime, // 表示的是maximumPoolSize当中等待任务的存活时间
unit, new LinkedBlockingQueue<Runnable>(), // 缓冲队列,用于存放等待任务,Linked的先进先出(无界队列)
Executors.defaultThreadFactory(), // 创建线程的工厂
new ThreadPoolExecutor.AbortPolicy() // 用来对超出maximumPoolSize的任务的处理策略
);
executor.allowCoreThreadTimeOut(true); // keepAliveTime同样作用于核心线程
}
/**
* 执行任务
*/
public void execute(Runnable runnable) {
if (runnable == null)
return;
executor.execute(runnable);
}
/**
* 从线程池中移除任务
*/
public void remove(Runnable runnable) {
if (runnable == null)
return;
executor.remove(runnable);
}
}