前言
由于线程池涉及的知识点比较多,会以上中下三篇文章来叙述;
当我们面试时,如果遇到了面试官让你说一下线程池,我们可以先讲一下线程池的核心思想:
1.复用线程,降低线程创建与销毁代价。
2.提升处理速度,避免了等待线程创建的时间。
3.池化,方便统一管理与监控。
总体实现
ThreadPoolExecutor是线程池的核心类,主要负责线程管理和任务分发
线程池本身就是一个生产-消费者模型,将任务生产与任务消费完全解耦,达到缓冲目的。
任务管理
当用户提交一个任务后,接下来这个任务将如何执行:
1.直接拿到核心线程进行任务执行
2.进入到缓冲队列,等待非核心线程
3.拒绝
源码如下:
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();
}
//进入到缓冲队列,等待非核心线程
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);
}
//拒绝
else if (!addWorker(command, false))
reject(command);
}
源码逻辑:
1.如果workerCount < corePoolSize 则启动新线程
2.如果workerCount >= corePoolSize 则加入缓冲队列,等待执行
3.如果 workerCount < maximumPoolSize ,并且缓冲队列已满,则启动新线程
4.workerCount > maximumPoolSize 执行拒绝策略
任务缓冲
线程池本身是个生产消费者模式,用户将任务生产到任务队列,然后线程从任务队列拿任务。线程池的队列是BlockingQueue,但是线程池可以通过不同的Queue实现不一样的存储策略。
ArrayBlockingQueue:规定大小的阻塞队列,元素先进先出,要指定容量。
LinkedBlockingQueue:默认大小为Integer.MAX_VALUE,要注意容量。
PriorityBlockingQueue:自定义实现compareTo()方法来置顶元素排序规则,不能保证同优先级元素的顺序。
SynchronousQueue:每一个put操作必须等待take操作,否则不能添加元素,如果有空闲线程则会重复使用,线程空闲了60s后会被自动回收。
任务拒绝
线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。
下篇会讲一下线程池中的worker是如何执行任务的,线程是如何复用的。