文章目录
目录
前言
线程池是一种多线程处理形式,它管理着一组线程,负责线程的创建、复用和销毁等操作。合理使用线程池可以提高系统的性能和资源利用率,下面主要从它要使用到的核心配置参数和使用流程两个方面来进行讲解。
一、线程池的基本概念
什么是线程池呢,线程池是一种管理和复用线程的机制,避免了频繁创建和销毁线程带来的性能开销。运行期间,线程池维护若干个线程,等待任务提交并执行。线程池就像一个容纳多个线程的容器,当有任务提交时,线程池会从内部的线程中选取一个空闲线程来执行该任务。如果没有空闲线程且线程池的线程数量未达到上限,会创建新的线程;若达到上限,任务可能会被放入任务队列等待执行。
二、线程池的核心配置参数
1.源代码解析
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
ThreadPoolExecutor:通过Woker工作线程、BlockingQueue阻塞工作队列 以及 拒绝策略实现了一个标准的线程池;通过 ThreadPoolExecutor 的构造函数可以自定义线程池的各种参数,这种方式更加灵活,推荐在生产环境中使用。从上述源代码可以看出来通过ThreadPoolExecutor类来创造的线程池包含七个核心配置参数:
1.【corePoolSize】线程池核心线程数:也可以理解为线程池维护的最小线程数量,核心线程创建后不会被回收。大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收;
CPU 密集型任务:线程数设置为 CPU核心数 + 1(避免上下文切换,充分利用 CPU 计算资源)。
IO 密集型任务:线程数设置为 2 * CPU核心数(IO 操作会导致线程阻塞,需要更多线程处理其他任务)
2.【maximumPoolSize】线程池最大线程数:线程池允许创建的最大线程数量;
3.【keepAliveTime】非核心线程线程存活时间:当可被回收的线程的空闲时间大于keepAliveTime,就会被回收。当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会被回收,直到线程池中的线程数不超过corePoolSize。
如果设置allowCoreThreadTimeOut = true,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
IO 密集型任务:可设置较长的存活时间(如 60 秒),避免频繁创建和销毁线程。
CPU 密集型任务:可设置较短的存活时间,及时释放资源。
通过allowCoreThreadTimeOut(true)可使核心线程也受keepAliveTime控制。
4.【TimeUnit】时间单位:参数keepAliveTime的时间单位;
5.【BlockingQueue】阻塞工作队列:用来存储等待执行的任务;
6.【ThreadFactory】线程工厂 : 用于创建线程,以及自定义线程名称,需要实现ThreadFactory接口;
7.【RejectedExecutionHandler】拒绝策略:当线程池线程内的线程耗尽,并且工作队列达到已满时,新提交的任务,将使用拒绝策略进行处理;
2.工作队列(4种)
1.ArrayBlockingQueue:基于数组实现的有界队列,读写线程不支持并发;
2.LinkedBlockingQueue:基于链表实现的无界队列(有界队列),读写线程支持并发。
3.SynchronousQueue:不存储任务,直接提交给线程执行。
4.PriorityBlockingQueue:优先级队列。
3.拒绝策略(4种)
1.AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException异常;
2.DiscardPolicy:丢弃任务,但是不抛出异常;
3.DiscardOldestPolicy:丢弃工作队列中的队头任务(即最旧的任务,也就是最早进入队列的任务)后,继续将当前任务提交给线程池;
4.CallerRunsPolicy:由原调用线程处理该任务 (谁调用,谁处理);
三、线程池的执行流程
线程池工作的源代码:
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();
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);
}
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be accepted.
* Invocation has no additional effect if already shut down.
*
* <p>This method does not wait for previously submitted tasks to
* complete execution. Use {@link #awaitTermination awaitTermination}
* to do that.
*
* @throws SecurityException {@inheritDoc}
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
线程池的执行流程可以分为以下几个主要步骤:
1. 提交任务
当有新任务提交到线程池时,线程池会开始按照其内部机制处理该任务。以下是一个简单的 Java 代码示例,展示如何向线程池提交任务:
2. 判断核心线程池是否已满
线程池会首先检查核心线程池(corePoolSize)中的线程数量。如果核心线程池中的线程数量小于 corePoolSize
,则会创建一个新的核心线程来执行该任务。即使此时核心线程池中存在空闲线程,也会优先创建新线程。
3. 判断工作队列是否已满
如果核心线程池已满,即核心线程数量达到了 corePoolSize
,那么新提交的任务会被放入工作队列(workQueue)中等待执行。工作队列通常有多种实现,如 ArrayBlockingQueue
、LinkedBlockingQueue
等。
4. 判断线程池是否已满
如果工作队列也已满,线程池会检查线程池中的线程总数是否达到了最大线程数(maximumPoolSize)。如果还未达到,则会创建一个新的非核心线程来执行该任务。
5. 执行拒绝策略
如果线程池中的线程总数已经达到了 maximumPoolSize
,并且工作队列也已满,那么线程池会执行拒绝策略(RejectedExecutionHandler)来处理新提交的任务。
综上所述,执行顺序为:核心线程、工作队列、非核心线程、拒绝策略。
工作流程如下图:
总结
总的来说,线程池工作流程如下:
- 任务提交:外部向线程池提交新任务。
- 核心线程处理:若核心线程数小于
corePoolSize
,创建新的核心线程执行任务。 - 任务入队:核心线程数达到
corePoolSize
后,新任务进入工作队列等待。 - 非核心线程处理:工作队列满时,若线程总数未达
maximumPoolSize
,创建非核心线程执行任务。 - 拒绝任务:线程总数达到
maximumPoolSize
且工作队列已满,执行拒绝策略处理新任务。