菜鸡每日一面系列打卡23天
每天一道面试题目
助力小伙伴轻松拿offer
坚持就是胜利,我们一起努力!
题目描述
谈谈你对线程池的理解(下)。
题目分析
在上一篇谈谈你对线程池的理解(上)中,菜鸡主要讲述线程池的概念,Java中的线程池以及线程池的生命周期。本文将结合源码介绍线程池的配置以及核心实现。
如果说上一篇是对线程池的整体把握,那么,这篇就是对线程池的细节剖析。接下来,随菜鸡一起去看看吧。
题目解答
01
线程池的配置
在上篇中,我们提到了四种常见的线程工厂。
newFixedThreadPool:创建固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量后不再变化。
newSingleThreadExecutor:单线程的Executor,创建单个工作线程执行任务,可以确保按照任务在队列中的顺序来串行执行。
newCachedThreadPool:创建可缓存的线程池,如果线程池的当前规模超过处理需求时,回收空闲线程,反之,创建新的线程。
newScheduledThreadPool:创建固定长度的线程池,并且能以延迟或定时的方式执行任务。
它们的实现都或直接或间接地使用到了一个非常重要的类ThreadPoolExecutor。这个类的构造方法设置了线程池的必要参数。我们从源码入手,看一下ThreadPoolExecutor类的构造参数有哪些必要参数。
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
}
由源代码可知,该构造方法有七个参数,我们来看一下每个参数的具体含义。
corePoolSize:线程池的基本大小,也即线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
maximumPoolSize:线程池的最大大小,表示可同时活动的线程数量的上限。
keepAliveTime:如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。
unit:keepAliveTime的单位。
workQueue:用于保存等待执行任务的队列。ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排列方法有3种:
无界队列:在任务急剧增加时容易导致资源耗尽;
有界队列:在队列满之后,需要相应的饱和策略来应对;
同步移交:在线程池达到最大线程数且无空闲队列时,需要相应的饱和策略来应对。
threadFactory:线程工厂,在ThreadFactory中只定义了一个方法newThread,每当线程池需要创建一个新线程时都会调用该方法。
handler:饱和策略。主要有4种:
AbortPolicy:终止,抛出RejectedExecutionException,默认饱和策略;
CallerRunsPolicy:将某些任务回退到调用者,降低新任务的流量;
DiscardPolicy:直接丢弃;
DiscardOldestPolicy:丢弃最早的未被处理的任务。
02
源码分析
了解线程池的参数之后,我们来看看ThreadPoolExecutor类的核心部分源码。
public class ThreadPoolExecutor extends AbstractExecutorService {
// 执行
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 当前任务数小于线程池的基本大小
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 线程池状态isRunning并且工作队列可以加入
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 通过addWorker(command, false)新建线程执行任务,若失败则拒绝。
else if (!addWorker(command, false))
reject(command);
}
// 平缓关闭
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 状态为SHUTDOWN
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
// 粗暴关闭
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 状态为STOP
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
}
上述主要分享了最常用的execute方法以及上一篇中提到的关闭线程池两种方式的源码。菜鸡在关键位置做了注释,在此就不再赘述。
其实,如果静下心来看,小伙伴们就会发现,JDK的源码条理性是很好的,各种设计的思维细细品味也都能有所领悟,因此,学习优秀的源码对个人成长是非常有帮助的。
如果不知道哪些是优秀的源码,那就从JDK源码看起,从常用的类库的源码看起,在学习原理的同时,学习设计,妙啊……
相关链接
以上便是菜鸡对线程池的总结下篇,供大家参考,结合上篇食用,效果更佳!
学习 | 工作 | 分享
????长按关注“有理想的菜鸡”
只有你想不到,没有你学不到