hello:大家好我是 小小小小小鹿,一枚Android程序猿。
我的座右铭是:做你想做的事吧,即使失败你不过还是原来那个默默无闻的小菜鸟而已。做你想成为的人吧,人生需要不同的尝试。
今天我想要和大家聊一聊线程池。在Android中,为了保证UI的流畅性,会将许多耗时的操作放到子线程中来完成。但是线程作为CPU调度的最小单位,创建、销毁和线程调度都需要较大的成本。因此线程的管理就显得尤为重要。并且,一些常用的框架比如Glide,Okhttp都有有自己的线程池配置,线程池也是Android面试过程中的高频问题。所以对于一个想要进阶高级Android开发人员来说,线程池是一个绕不开的话题。
前言
为什么写这篇博客,作为一个Android开发者,日常使用线程池的频率相对较少,网上又许多的关于线程池的博客。真的有必要写这个东西吗?辗转思考我还是决定要写一篇关于线程池的文章,因为站在不同的角度可能有不同的理解,同时别人的文章也并没有解答我想要的问题。比如
- 核心线程和非核心线程的区别?怎么来进行维护的?线程池的线程复用和我们的对象池对象重用一样吗?
- workQueue存储量无限大,这个非核心线程怎么启动?
- 非核心线程池到底会不会从workQueue取数据?
- java线程池的设计理念。为什么是核心线程-->workQueue --> 非核心线程 而不是 核心线程-->非核心线程 而不是 --> workQueue 的顺序插入?
2022年掘金的愿望:升级到lv4,写出一篇点赞数超过100的文章。目标从这篇文章开始。评论、点赞、关注都是对我最大的鼓励。
线程池配置参数
线程池可以通过ThreadPoolExecutor来创建,ThreadPoolExecutor有多个构造函数,但是最终会通过this调用参数最多的这个。我们来看看。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数解析:
-
corePoolSize: 线程池核心线程数最大值
-
maximumPoolSize: 线程池最大线程数大小
-
keepAliveTime: 线程池中非核心线程空闲的存活时间大小,但是allowCoreThreadTimeOut(boolean value) 设置为 true 时 这个时间也会作用于核心线程。
-
unit: 线程空闲存活时间单位
-
workQueue: 线程池中的任务队列,我们提交给线程池的runnable会被存储在这个对象上。
-
线程池的分配遵循这样的规则:当线程池中的核心线程数量未达到最大线程数时,启动一个核心线程去执行任务;
-
如果线程池中的核心线程数量达到最大线程数时,那么任务会被插入到任务队列中排队等待执行;
-
如果在上一步骤中任务队列已满但是线程池中线程数量未达到限定线程总数,那么启动一个非核心线程来处理任务;
-
threadFactory: 用于设置创建线程的工厂,通过实现newThread返回一个新的线程
-
handler: 线城池的饱和策略事件,主要有四种类型。
- AbortPolicy(抛出一个异常,默认的)
- DiscardPolicy(直接丢弃任务)
- DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
- CallerRunsPolicy(交给线程池调用所在的线程进行处理)
ThreadPoolExecutor关键成员
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
//通过位操作获取线程池的运行状态 runState
private static int runStateOf(int c) { return c & ~CAPACITY; }
//通过位操作获取线程池的有效线程个数 workerCount
private static int workerCountOf(int c) { return c & CAPACITY; }
//将runState与workerCount合成 ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
ctl是一个线程安全的int,它的高三位用来存储线程池的状态(runState),低29位用来存储线程池内有效线程的个数(workerCount)。需要注意的是workerCount值可能暂时与活动线程的实际数量不同。
runState生命周期
runState 提供主要的生命周期控制,其值为:
- RUNNING:接受新任务,处理排队任务 。
- SHUTDOWN:不接受新任务,但处理排队任务 。
- STOP:不接受新任务,不处理排队任务,以及中断正在进行的任务 。
- TIDYING :所有任务都已终止,workerCount 为零,转换到状态 TIDYING 的线程将运行 terminate() 钩子方法 。
- TERMINATED: terminate() 已完成。
线程池的执行流程
向线程池提交任务有execute、submit、invokeAny 、invokeAll四种方式,但是他们最终都会被包装成调用execute的方式,这里不去探讨其他方式调用的实现。就以execute分析线程池的执行流程。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//workerCountOf从ctl中解析出当前工作线程的数量
//如果当前的工作线程数小于核心线程数,则新创建一个线程进行工作
if (workerCountOf(c) < corePoolSize) {
//添加核心线程成功,则直接退出
if (addWorker(command, true))
return;
//当前核心线程添加失败,重新获取ctl的值
c = ctl.get();
}
//当前线程池处于RUNNING状态并且成功的添加到了工作队列
//只有运行状态 workQueue才能添加任务
if (isRunning(c) && workQueue.offer(command)) {
//再次获取ctl并进行RUNNING状态判断,如果不是RUNNING状态,
//尝试从workQueue 移除刚才添加的任务,如果移除成功,执行拒绝策略。
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
//判断当前工作线程的数量,如果为0 则尝试创建一个非核心线程
//来从工作队里中读取处理任务
//什么时候工作线程会为0?为什么要在这个位置尝试添加一个非核心线程?
//情况1: 线程池配置的核心线程数为0 workQueue能够添加任务,此时必须有线程处理workQueue中的任务,
//否则直到workQueue饱和前,任务都不会被处理。如果workQueue是无界的,那么当任务堆积过多就只能等待oom。
//因此需要创建一个非核心线程来处理。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//代码流转到这个位置有如下情况
//情况1:当前状态不是 RUNNING
//情况2:当前是RUNNING 状态,但是核心线程数和workQueue都已经到达最大值。
//尝试添加一个非核心线程处理当前任务,如果失败执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
execute执行流程如下:
- 如果当前存活的线程数小于核心线程,则直接创建一个线程执行当前任务
- 如果当前核心线程数已经达到最大值且工作队列能够存储任务,那么将任务添加进入队列等待执行。
- 在核心线程数已经达到最大值的情况下,工作队列不能接受任务,如果可以创建非核心线程,那么创建非核心线程执行,否则执行任务拒绝策略。
addWorker代码分析
addWorker的作用是尝试在线程池中创建一个线程,第一个参数firstTask,代表第一个要执行的任务,第二个core 为true代表要创建的是核心线程,否则创建非核心线程。不论创建的是核心线程还是非核心线程,线程一旦创建就不不断的读取workQueue队列中的任务进行处理,直到没有任务处理的时候,非核心线程会因为超时而被回收,核心线程依据allowCoreThreadTimeOut是否为true来决定是否被回收。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
//获取当前运行状态
int rs = runStateOf(c);
// 再次回顾 runState生命周期,在SHUTDOWN状态下,不会接收新任务可以创建线程处理已经存在的任务
// rs >= SHUTDOWN 即不会在接收新的任务,
// rs == SHUTDOWN 在SHUTDOWN状态下可以处理workQueue的任务
// firstTask 当firstTask不为空时,说明添加当前线程是为了执行新任务
// workQueue.isEmpty() 为true的时候,说明工作队列已经处理完毕,不需要在创建线程
//总体上来说就是 在不能接收新任务的时候,判断是否可以创建线程处理workQueue的任务,能够创建则继续否则直接返回false 创建失败
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// wc >= CAPACITY CAPACITY是二进制是29个1 此时意味着当前的线程总数已经到了能够记录的极限,不能在创建线程
//根据创建的类型是否是核心线程将wc来比较corePoolSize或maximumPoolSize来判断是否能创建线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//尝试将当前记录的工作线程数加一,如果成功退出上一个循环
//需要注意的是这里的加一只是计数器加一,实际上还没有创建工作线程
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//因为尝试将当前记录的工作线程数加一失败,说明上一次获取的ctl并不精确,
//用新获取的ctl状态与上次的状态对比,如果状态发生改变,重新执行能否创建线程的判断逻辑,
//否则说明原来不准确的部分只是低29位即workerCount部分,继续执行当前循环,尝试将workerCount 加1
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//在计数器加一后,创建一个Worker Worker实现了Runnable并持有当前需要需要第一个执行的任务,
//同时在构造方法中会通过线程池配置的threadFactory来创建一个新的线程。并且将自己传递给新创建的线程。
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
//再次判断当前线程池的状态, rs < SHUTDOWN 即为RUNNING 可以直接添加
//rs == SHUTDOWN && firstTask == null 如果处于SHUTDOWN 那么线程
//池中创建新的线程只能是处理workQueue中的任务,因此firstTask必须为null
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//启动新创建的线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//如果线程启动失败,调用addWorkerFailed
//addWorkerFailed 内部会移除新创建的weork 并且对workerCount计数器减一
addWorkerFailed(w);
}
return workerStarted;
}
Worker类的工作
在前面我们提到Worker自身实现了Runnable并且将自身传递给了新创建的线程。
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
runWorker函数的执行
Worker自身实现了Runnable并且将自身传递给了新创建的线程也就意味着当线程启动的时候会调用Worker#run而它会直接调用ThreadPoolExecutor#runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//如果task不为空,会优先执行这个task
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//task 不为空的时候优先处理task 即刚才添加的任务
//task为空 即第一个任务处理完毕或者没有指定第一个任务 从workQueue中读取
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
// 保证:如果线程池正在停止,那么要保证当前线程是中断状态,
//否则要保证当前线程不是中断状态
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//开始执行的回掉子类自己实现
beforeExecute(wt, task);
Throwable thrown = null;
try {
//执行任务
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//执行完成之后的回调,子类自己实现
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//没有可用任务时调用到这个位置
processWorkerExit(w, completedAbruptly);
}
}
runWorker的工作过程如下:
- 循环的获取能够处理的task
- 在任务的执行过程中 如果线程池正在停止,那么要保证当前线程是中断状态,否则要保证当前线程不是中断状态
- 如果没有可用的任务或者任务执行过程中出现异常调用processWorkerExit退出当前线程执行
getTask任务的获取
private Runnable getTask() {
//超时标记
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//当前状态大于等于STOP 不接受新的任务并且不处理工作队列的消息
//线程计数器减一,返回null
//当前状态为SHUTDOWN 且workQueue为空 说明没有需要处理的消息
//线程计数器减一 返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
//workQueue 获取任务是否需要超时控制 标记
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//wc > maximumPoolSize 可能是在线程池的运行过程中更改了maximumPoolSize
// timed && timedOut 表示有超时控制并且发生了超时
//wc > 1 || workQueue.isEmpty() 是否运行回收工作的线程 当workQueue 不为空的
//时候至少需要有一个线程来处理工作队列的消息
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//阻塞的从workQueue中获取任务,根据timed标记判断是否需要触发超时
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
//如果获取的任务为空 说明当前任务获取超时了。
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask以阻塞的方式从workQueue中获取需要处理的任务。在下列情况会返回null
- 有超过maximumPoolSize的线程在处理工作队列的任务
- 线程池停止
- 线程池关闭并且工作队列为空
- 在需要超时的时候,获取任务超时
processWorkerExit
当getTask函数或者runWorker因为异常退出while循环时,会调用
processWorkerExit来清理工作线程。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//如果因为异常终止,将计数器减一
//如果不是异常终止,计数器减一的工作在getTask中完成
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//记录完成的任务数
completedTaskCount += w.completedTasks;
//从集合中移除Worker,标志着在processWorkerExit结束后当前的工作线程运行结束
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
int c = ctl.get();
//比STOP 状态小的有 RUNNING SHUTDOWN 都需要处理工作队列的任务
if (runStateLessThan(c, STOP)) {
//如果当前线程因为异常终止,则创建新的线程继续处理工作队列的任务
if (!completedAbruptly) {
//根据是否允许核心线程超时来判断当前线程池最小存在的线程
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果工作队列中有任务需要处理,那么当前线程池至少要保留一个线程
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//如果当前线程池的工作线程数 大于等于最小的线程数,则退出,否则添加一个工作线程
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
tryTerminate
在每一个清理工作线程的时候都会调用tryTerminate尝试终止线程池
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//如果当前处于RUNNING 表示当前线程池正在运行 直接退出
//如果处于 TIDYING TERMINATED 表示线程池正在结束 直接退出
//如果处于 SHUTDOWN 并且工作队列不为空 表示线程池还有需要处理的任务 直接退出
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//代码能够运行到这个位置有两中可能
//1.当前处于STOP状态
//2.当前状态为SHUTDOWN但是workQueue为空,没有需要处理的任务
//当前工作线程不为0 尝试中断一个线程
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 这里尝试设置状态为TIDYING,如果设置成功,则调用terminated方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
// 设置状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
线程池的状态变化(生命周期)
要终止一个线程池,可以通过ThreadPoolExecutor提供的shutdown或者shutdownNow来完成。
shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//将状态置为SHUTDOWN
advanceRunState(SHUTDOWN);
//中断所有线程池中所有处于闲置状态的线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
}
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//将状态置为STOP
advanceRunState(STOP);
//终止所有线程池中已经开始的线程
interruptWorkers();
//将任务从workQueue中移除
tasks = drainQueue();
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
return tasks;
}
即调用shutdown会将状态置为SHUTDOWN并继续处理workQueue中的任务,调用shutdownNow会将状态置为STOP在终止所有线程后,将工作队列中的任务主动移除。整个线程池会经历下面的状态变化。
线程池的异常处理
线程池的异常处理主要有下面的方式
- 给线程设置默认未处理的异常
- 重写ThreadPoolExecutor#afterExecute处理异常
- 通过try--catch进行异常捕获
常见的几种工作队列
线程池接受的工作队列接收的是BlockingQueue接口,它的实现有下面几个类
java实现的阻塞队列分为两种,单端Queue和双端Deque。在ThreadPoolExecutor线程池中使用的阻塞队列主要是单端队列。在java1.7中增加了ForkJoinPool,它主要使用的是双端队列。但是这篇文章主要说的是ThreadPoolExecutor因此不对双端队列进行讨论
- ArrayBlockingQueue
ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量
- LinkedBlockingQueue
LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列
- DelayQueue DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
- PriorityBlockingQueue PriorityBlockingQueue(优先级队列)是具有优先级的无界阻塞队列;
- SynchronousQueue SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。
ThreadPoolExecutor提供的几种线程池
ThreadPoolExecutor默认提供了4中线程池配置
- newFixedThreadPool (固定数目线程的线程池)
- newCachedThreadPool(可缓存线程的线程池)
- newSingleThreadExecutor(单线程的线程池)
- newScheduledThreadPool(定时及周期执行的线程池)
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
特点:
核心线程数和最大线程数相同,即没有非核心线程池;
采用LinkedBlockingQueue队列,理论上可以存储无线多个任务,可能因为任务数过多造成oom
适用场景:
newFixedThreadPool由于线程数量固定,能够有效的控制并发数量,适合CPU密集型的任务。
newCachedThreadPool
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
特点:
没有核心线程,最大线程数为Integer.MAX_VALUE,即非核心线程数为Integer.MAX_VALUE。非核心线程的超时时间是60s,即超过60s后线程会被回收,在没有任务需要处理的情况下不会占用系统资源。采用SynchronousQueue队列,当没有空闲线程的情况下,每添加一个任务就会创建一个线程,极端情况下会耗尽CPU资源。
使用场景:
由于CachedThreadPool在没有空闲线程的情况下,每添加一个任务就会创建一个线程的特性,每一个添加的任务都能够得到较快的执行,并且没有对线程数做限制,它比较适合于量大而短期的小任务。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
特点:
newSingleThreadExecutor和newFixedThreadPool线程池基本一样,区别在于newSingleThreadExecutor中的核心线程数为1而newFixedThreadPool核心线程数可以指定。由于newSingleThreadExecutor只有一个核心线程。因此所有提交到线程池的任务都会被串行执行。
适用场景:
适用于有固定顺序的任务
newScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
特点:
核心线程池固定,线程总数无限大,并且,newScheduledThreadPool与其它三者线程池的实现不同,它通过继承ThreadPoolExecutor来自定义线程池的调度。
在这个线程池执的任务有三种方式
- schedule延时执行 只会执行一次
- scheduleAtFixedRate 按照固定时间间隔执行(相对于任务的开始时间)
- scheduleWithFixedDelay 按照固定时间间隔执行 (相对于任务的结束时间)
关于scheduleAtFixedRate scheduleWithFixedDelay 两者都是按照固定时间间隔执行,但是他们也有一些细微的差别。
同时线程池以固定时间间隔执行任务相较Timer定时器更加精确。
同时它的工作机制和ThreadPoolExecutor还略有不同,每次提交任务的时候会先将任务进行封装处理,加入工作队列,在核心线程池未满的情况下直接开启一个核心线程来处理。如果核心线程数为0那么开启一个非核心线程来处理。
适用场景:
周期性执行任务的场景,需要限制线程数量的场景
Glide中的动画线程池配置有用吗?
在Glide线程池中我们分析了Glide的各种线程池配置。这里在回顾下Glide中的动画线程池
public static GlideExecutor newAnimationExecutor() { int bestThreadCount = calculateBestThreadCount(); int maximumPoolSize = bestThreadCount >= 4 ? 2 : 1; return newAnimationExecutor(maximumPoolSize, UncaughtThrowableStrategy.DEFAULT); } public static GlideExecutor newAnimationExecutor( int threadCount, UncaughtThrowableStrategy uncaughtThrowableStrategy) { return new GlideExecutor( new ThreadPoolExecutor( 0 /* corePoolSize */, threadCount, KEEP_ALIVE_TIME_MS, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(), new DefaultThreadFactory( ANIMATION_EXECUTOR_NAME, uncaughtThrowableStrategy, true))); } 复制代码AnimationExecutor没有核心线程,非核心线程数量根据Cpu核心数来决定,当Cpu核心数大于等4时 非核心线程数为2,否则为1。
它的工作队列采用PriorityBlockingQueue无界队列,根据前面的分析,任务会先尝试开启核心线程池进行处理,这里核心线程为0,核心线程不能处理的时候将任务加入工作队列(如果此时工作线程总数为0,会开启一个非核心线程处理任务),即Glide的动画线程池的配置将永远只有一个非核心线程处理任务。因此Glide线程最大数量的计算是无效的。
代码测试:观察在多个线程的情况下任务是否串行执行
//因为PriorityBlockingQueue 为优先级队列需要比较因此自己实现了Comparable
abstract static class TestRunnable implements Runnable,Comparable{
@Override
public int compareTo(Object o) {
return -1;
}
}
private static void testGlideAnimationPool(){
//线程池没有核心线程,最大线程数为4
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
0 /* corePoolSize */,
4,
100,
TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>()
, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
System.out.println("create thread");
return new Thread(r);
}
});
threadPoolExecutor.execute(new TestRunnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是第一个sleep 5s");
}
});
threadPoolExecutor.execute(new TestRunnable() {
@Override
public void run() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是第二个sleep 4s");
}
});
threadPoolExecutor.execute(new TestRunnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是第三个sleep 3s");
}
});
}
结果:
可以看到整个线程池只创建了一次线程,因此Glide动画线程池的线程数计算是无效的。
OkHttp中的线程池是如何配置的
okhttp的线程池配置代码如下:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
可以发现okhttp的线程池配置和newCachedThreadPool一致,它比较适合于量大而短期的小任务。在没有网络请求的时候会将线程进行回收,几乎不占用任何资源。
问题回答
-
核心线程和非核心线程的区别?怎么来进行维护的?线程池的线程复用和我们的对象池对象重用一样吗?
在线程池中核心线程的创建和非核心线程的创建没有太大的区别,线程一但创建就会不断的读取处理工作队列的任务,直到某个线程因为获取任务超时而被回收,如果没有设置allowCoreThreadTimeOut为true核心线程会一直阻塞,直到工作队列获取到任务才会被执行。
线程池线程重用和对象池对象重用完全不同,线程重用指的是线程以阻塞的方式不断循环的从工作队列中获取任务,而对象重用指的是内存重用。
-
workQueue存储量无限大,这个非核心线程怎么启动?
如果有核心线程,非核心线程不会被启动,如果核心线程数为0,那么会启动一个非核心线程处理当前工作队列的任务
-
非核心线程池到底会不会从workQueue取数据?
线程一旦创建就会从工作队列获取任务,直到因为超时被销毁
-
java线程池的设计理念。为什么是核心线程-->workQueue --> 非核心线程 而不是 核心线程-->非核心线程 --> workQueue 的顺序插入?
核心线程-->workQueue --> 非核心线程的设计的内在逻辑是,当自身能够处理的时候优先自己处理,当堆积的任务在超过一定的阈值的时候开启非核心线程进行处理。举个例子,it公司当接到客户的需求的时候,产品经理会将需求下发给研发,如果还有空余的研发(核心线程)会将任务直接分配给研发,如果大家都有工作在忙,那么将客户的需求排期(加入工作队列),但是当需求非常多的时候,远远超过客户要求完成的时间,这个时候公司可以招聘外包人员来协助公司员工完成任务。当项目完结过后,外包人员离开,公司员工继续在公司工作。这个也比较契合现实情况当核心线程不能处理当前的所有任务时,开辟非核心线程进行处理。
如果设计成核心线程-->非核心线程 --> workQueue 的顺序 就意味着当有多余任务来临的时候优先招聘外包员工,在正式员工和外包原都有任务的时候才会对需求进行排期。
未尽事项:
newScheduledThreadPool底层实现原理
ForkJoinPool 线程池分析
文章参考: