池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。线程的创建和销毁都需要映射到操作系统,因此其代价是比较高昂的。出于避免频繁创建、销毁线程以及方便线程管理的需要,线程池应运而生。
线程池的优势:
- 降低资源消耗:线程池通常会维护一些线程(数量为
corePoolSize
),这些线程被重复使用来执行不同的任务,任务完成后不会销毁。在待处理任务量很大的时候,通过对线程资源的复用,避免了线程的频繁创建与销毁,从而降低了系统资源消耗。 - 提高响应速度:由于线程池维护了一批 alive状态的线程,当任务到达时,不需要再创建线程,而是直接由这些线程去执行任务,从而减少了任务的等待时间。
- 提高线程的可管理性:使用线程池可以对线程进行统一的分配,调优和监控。
一、重要参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数名称
说明
corePoolSize
核心线程数。当线程数小于该值时,线程池会优先创建新线程来执行新任务。
maximumPoolSize
线程池所能维护的最大线程数。当核心线程全部繁忙且任务队列打满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize
这个上限。
keepAliveTime
空闲线程的存活时间。当非核心线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut
参数设置为true
后,核心线程也会被回收。
unit
keepAliveTime
参数的时间单位。
workQueue
任务队列,用于缓存未执行的任务。
threadFactory
线程工厂。指定线程池创建线程的方式。
handler
拒绝策略。当线程池和任务队列均处于饱和状态时,使用拒绝策略处理新任务。默认是AbortPolicy,即直接抛出异常。
二、线程创建规则
- 线程数量小于 corePoolSize,直接创建新线程处理新的任务
- 线程数量大于等于 corePoolSize,workQueue 未满,则缓存新任务
- 线程数量大于等于 corePoolSize,但小于 maximumPoolSize,且 workQueue 已满。则创建新线程处理新任务
- 线程数量大于等于 maximumPoolSize,且 workQueue 已满,则使用拒绝策略处理新任务
条件
做法
线程数 <corePoolSize
创建新线程
线程数 ≥corePoolSize,且 workQueue 未满
缓存新任务
corePoolSize ≤ 线程数 <maximumPoolSize,且workQueue 已满
创建新线程
线程数 ≥maximumPoolSize,且 workQueue 已满
使用拒绝策略处理
三、阻塞队列
通过 JDK 文档介绍,我们可知道有3中类型的容器可供使用,分别是**
同步队列
,有界队列
和无界队列
。对于有优先级的任务,这里还可以增加优先级队列
**。
实现类
类型
说明
SynchronousQueue
同步队列
这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。
ArrayBlockingQueue
有界队列
基于数组的阻塞队列,按照 FIFO 原则对元素进行排序。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了。
LinkedBlockingQueue
无界队列
基于链表的阻塞队列,按照 FIFO 原则对元素进行排序。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。
PriorityBlockingQueue
优先级队列
具有优先级的阻塞队列。存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。
四、拒绝策略
线程数量大于等于 maximumPoolSize,且 workQueue 已满,则使用拒绝策略处理新任务。Java 线程池提供了4中拒绝策略实现类。
实现类
说明
AbortPolicy(默认)
丢弃新任务,并抛出RejectedExecutionException。
DiscardPolicy
不做任何操作,直接丢弃新任务。
DiscardOldestPolicy
将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。
CallerRunsPolicy
直接运行这个任务的run
方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。
五、源码解读
// 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int workerCountOf(int c) {
return c & CAPACITY;
}
private final BlockingQueue<Runnable> workQueue;
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
// 步骤1:判断线程池当前线程数是否小于线程池大小
if (workerCountOf(c) < corePoolSize) {
// 增加一个工作线程并添加任务,成功则返回,否则进行步骤2
// true代表使用coreSize作为边界约束,否则使用maximumPoolSize
if (addWorker(command, true))
return;
c = ctl.get();
}
// 步骤2:不满足workerCountOf(c) < corePoolSize或addWorker失败,进入步骤2
// 校验线程池是否是Running状态且任务是否成功放入workQueue(阻塞队列)
// 如果队列已满,则 offer 方法返回 false。否则,offer 返回 true
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次校验,如果线程池非Running且从任务队列中移除任务成功,则拒绝该任务
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池工作线程数量为0,则新建一个空任务的线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 步骤3:如果线程池不是Running状态或任务入列失败,尝试扩容maxPoolSize后再次addWorker,失败则拒绝任务
else if (!addWorker(command, false))
reject(command);
}
参考
Java 并发常见知识点&面试题总结(进阶篇) | JavaGuide