三大分析法分析android线程池主要分为四部分,第一部分是4W2H分析线程池,第二部分是MECE分析线程池,第三部分是SCQA分析线程池,最后一部分是结语。
其中,4W2H分析线程池主要围绕线程池提出了6个高价值问题。
其中,MECE分析线程池主要分为线程池基本操作、线程池生命周期、线程池工作原理、线程池代码案例分析、线程池的性能优化、线程池注意事项、线程池线程数量确定和线程池业务防劣化8部分。
一、4W2H分析线程池
1. 线程池具体定义
线程池是一种管理和调度线程的机制,线程池可以控制线程的数量,确保线程有效地工作,并且可以重复使用以前创建的线程,从而减少系统的开销。
可以 点击 “此处” 即可 免费获取 学习资料 以及 更多Android学习笔记+源码解析+面试视频
2. 线程池使用原因
如果不使用线程池,每个任务都新开一个线程处理 for循环创建线程,开销太大,我们希望有固定数量的线程,来执行这1000个线程,就避免了反复创建并销毁线程所带来的开销问题
3. 线程池使用方式
3.1 线程池API文档
API | 简介 |
---|---|
isShutdown | 判断当前线程的状态是否是SHUTDOWN,即是否调用了shutdown方法 |
isTerminating | 当前线程池的状态是否小于TERMINATED,并且大于等于SHUTDOWN,即当前线程是否已经shutdown并且正在终止。 |
isTerminated | 线程池是否终止完成 |
awaitTermination | 等待直到线程状态变为TERMINATED |
finalize | 重新Object的方法,当线程池对象被回收的时候调用,在这里调用shutdown方法终止线程,防止出现内存泄漏 |
prestartCoreThread | 预先启动一个核心线程 |
prestartAllCoreThreads | 预先启动所有的核心线程 |
remove | 从任务队列中移除指定任务 |
purge | 从队列中移除所有的以及取消的Future任务 |
getPoolSize | 获取线程池中线程的数量,即Worker的数量 |
getActiveCount | 获取线程池中正在执行任务的线程Worker数量 |
getLargestPoolSize | 获取线程池曾经开启过的最大的线程数量 |
getTaskCount | 获取总的任务数量,该值为每个Worker以及完成的任务数量,以及正在执行的任务数量和队列中的任务数量 |
getCompletedTaskCount | 获取Worker以及执行的任务数量 |
beforeExecute | 任务执行前调用 |
afterExecute | 任务执行后调用 |
terminated | 线程终止时调用,用来回收资源 |
3.2 线程池基础结构
线程池的基础结构分为三部分: 阻塞队列BlockingQueue、核心参数和Worker工作线程。
3.2.1 阻塞队列
线程池ThreadLocal是一个阻塞队列BlockingQueue
private final BlockingQueue<Runnable> workQueue;
阻塞队列BlockingQueue主要是用来提供任务缓冲区的功能,工作线程从阻塞队列BlockingQueue中取出任务来执行。
线程池中存放任务用的是offer方法,取出任务用的是poll方法。 阻塞队列BlockingQueue有三种通用策略
直接提交
直接提交,当一个线程提交一个任务的时候,如果没有一个对应的线程来取任务,提交阻塞或者失败。同样的当一个线程取任务的时候,如果没有一个对应的线程来提交任务,取阻塞或者失败。
SynchronousQueue就是这种队列的实现,这种队列通常要求maximumPoolSizes最大线程数量是无界的,避免提交的任务因为offer失败而被拒绝执行。
当提交任务的速率大于任务执行的速率的时候,这种队列会导致线程数量无限的增长。
无界队列
无界队列,无界队列实现的例子是LinkedBlockingQueue,当核心线程都处于忙碌的情况的时候, 提交的任务都会添加到无界队列中,不会有超过核心线程数corePoolSize的线程被创建。
这种队列可能适用于任务之间都是独立的,任务的执行都是互不影响的。
例如,在一个web服务器中,这种队列能够用来使短时间大量的并发请求变得更加平滑,当提交任务的速率大于任务执行的速率的时候,这种队列会导致队列空间无限增长。
有界队列
有界队列,有界队列实现的例子是ArrayBlockingQueue,使用该队列配合设置有限的maximumPoolSizes可以防止资源耗尽,这种情况下协调和控制资源和吞吐量是比较困难的。
队列大小和maximumPoolSize的设置是比较矛盾的:使用容量大的队列和较少的线程资源会减少CPU、OS资源、线程上下文切换等的消耗,但是会降低系统吞吐量。
如果任务频繁的阻塞,例如任务是IO密集的类型,这种情况下系统能够调度更多的线程。使用小容量队列,就要要求maximumPoolSize大一些,从而让CPU保持忙碌的状态,但是可能出现线程上下文切换频繁、线程数量过多调度困难已经创建线程OOM导致资源耗尽的情况,从而降低吞吐量。
SynchronousQueue vs LinkedBlockingQueue vs ArrayBlockingQueue
SynchronousQueue
SynchronousQueue适用于请求响应要求无延迟,请求 并发 量较少的场景。
当线程Worker没有从队列取任务的时候,offer返回false,直接开启线程。当Worker从队列取任务的时候,该任务可以直接提交给Worker执行。
因此,这种线程池不会出现等待的情况,响应速度很快。
队列的缺点是提交任务速度大于任务执行速度时,会导致线程无限增长,因此,使用场景需要是并发量较少的情况。
例如,在OkHttp框架中执行HTTP请求就是用的这种队列构建的线程池。
LinkedBlockingQueue
LinkedBlockingQueue适用于并发量高,任务之间都是独立的,互不影响的场景。
比如在web服务器中,面对瞬时大量请求的涌入,可以更加平滑的处理,从而起到削峰的作用,并且防止线程资源的耗尽。
ArrayBlockingQueue
ArrayBlockingQueue是介于前两者之间的队列,可以协调系统资源和吞吐量之间的平衡。
2.3.2.2 核心参数
一个Worker代表一个工作线程,wrokers存储的是线程池中所有的工作线程。
工作线程的核心参数有如下
private final HashSet<Worker> workers = new HashSet<Worker>();
// ---------------------------------------------------------------
// 曾经开启过的最大的线程数量
private int largestPoolSize;
// 完成的任务的数量
private long completedTaskCount;
// ---------------------------------------------------------------
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private volatile long keepAliveTime;
private volatile boolean allowCoreThreadTimeOut;
private volatile int corePoolSize;
private volatile int maximumPoolSize;
这几个变量都是用户设置的参数变量,为了保证参数设置的可见性,所有参数都使用volatile修饰。 ThreadFactory是线程创建工厂,提供线程创建和配置的接口,这里使用的是工厂方法模式,默认的实现是DefaultThreadFactory。
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
//注意,Runnable r 就是工作线程接口Worker,需要传到Thread中。
public Thread newThread(Runnable r) {
Thread t = new Thread(group,r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
默认的线程工厂创建的线程名称为pool-poolNumber-thread-threadNumber,第一个线程池第一个线程名称为pool-0-thread-0,第二个线程名称为pool-0-thread-1,第二个线程池第一个线程名称为pool-1-thread-0,第二个线程名称为pool-1-thread-1,依次类推。
- RejectedExecutionHandler
- 是当任务 被拒绝时的执行接口,提供了4种实现策略,默认采取AbortPolicy策略,如果不设置,线程池在拒绝任务的时候会抛出异常。
- CallerRunsPolicy
- 在当前提交线程直接运行该任务。
- AbortPolicy
- 直接抛出RejectedExecutionException异常。