引子
通常我们在需要异步调用一个任务的时候,会手动创建一个线程去执行异步任务,但是每次手动启动一个线程,使用完毕就丢弃了,太浪费资源。所以线程池的概念就出来了,就跟数据库连接池一样。
通常创建和销毁比较耗费资源的连接都会使用“池”来管理。
线程池的好处
- 降低资源消耗:通过已创建的线程来执行异步任务,降低了线程创建和销毁的消耗
- 提高响应速度:异步任务不需要等待线程创建的时间,直接使用创建好的线程工作
- 管理线程:线程池可以统一管理线程的调度,不至于创建过多的线程
ThreadPoolExecutor
线程池的核心类是ThreadPoolExecutor,我们先通过ThreadPoolExecutor类的构造方法来了解线程池:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数分析
参数很多,同样的注释也很详细,下面我们来一个个的讲解一下这些参数:
- corePoolSize:核心线程池大小
- maximumPoolSize:线程池最大容量大小
- keepAliveTime:线程池空闲时,线程存活的时间
- TimeUnit:时间单位
- ThreadFactory:线程工厂
- BlockingQueue:任务队列
- RejectedExecutionHandler:线程拒绝策略
名词解释
下面先来解释一些线程池中的名词:
- 核心线程:线程池主要用于执行任务的是“核心线程”,“核心线程”的数量是创建线程时所设置的corePoolSize参数决定的。线程池会尽量保持corePoolSize数量的线程数。
- maximumPoolSize参数也是当前线程池允许创建的最大线程数量。
- 非核心线程:一旦任务数量过多,线程池将创建“非核心线程”临时帮助运行任务。maximumPoolSize-corePoolSize的值就是线程池可以临时创建的“非核心线程”的最大数量。超过maximumPoolSize的任务会执行拒绝策略。
- keepAliveTime参数和timeUnit参数配合使用。keepAliveTime参数指明等待时间的量化值,timeUnit指明量化值单位。例如keepAliveTime=1,timeUnit为TimeUnit.MINUTES,代表空闲线程的回收阀值为1分钟。
- 回收机制:空闲线程的回收意思是非核心线程空闲时间达到回收阈值之后,会被销毁,直到线程池的线程数量达到corePoolSize为止。需要注意的是,并不是说只有非核心线程中的线程才会被回收,而是达到阈值的线程都会被回收,直到线程数量达到corePoolSize为止。
- 拒绝策略:当线程池中线程数量超过maximumPoolSize时,再添加任务会拒绝接受任务,直接执行拒绝策略。
- 等待队列:当线程数量达到corePoolSize后,新添加的任务不会直接执行,而是送入一个队列等待执行。
任务的分配
在了解了线程池中的一些名词概念后,我们来了解一下线程池是如何处理一个任务的。
我们可以调用execute方法,execute方法入参一个Runnable对象来给线程池添加任务:
- 添加任务时首先检查线程池中线程数量是否达到corePoolSize,如果没达到的话,会创建一个新的线程来执行任务,无论已创建的线程是否空闲
- 如果线程池中的线程数量已经达到corePoolSize,那么会将任务添加到任务队列中。直到有一个线程空闲了,会从等待队列中取出一个任务执行
- 如果这个等待队列已满,不能接受新的任务,那么会创建一个非核心线程,直接执行任务
- 如果核心线程数量+非核心线程数量已经达到maximumPoolSize,并且等待队列已满,那么再接收到新的任务会执行拒绝策略,拒绝执行这个任务
Executors创建线程池
在上一部分,已经讲到了如何使用ThreadPoolExecutor类创建线程池,已经线程池的执行原理和规则。
但是大家可能会发现使用ThreadPoolExecutor类创建线程池显得有点麻烦了,需要自己定义各种size的大小、等待队列、拒绝策略等等。
所以就出现了Executors类来帮我们构建一些简单的线程池。
Executors类主要能帮我们创建四种线程池
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
使用Executors创建线程池可以缺省很多参数,创建线程池变得简单了许多,但是往往就因为这些缺省的参数,可能会导致大家忽略了ThreadPoolExecutor中各个参数的含义,从而导致创建出来的线程池并不满足业务需求,从而导致资源的浪费。
所以这里不建议大家使用Executors类来创建线程池,对于Executors类创建的四种线程池可以去了解一下,但是线程池还是使用ThreadPoolExecutor类来创建会更加符合我们的需求。
喜欢这篇文章的朋友,欢迎长按下图关注公众号lebronchen,第一时间收到更新内容。