1、线程池是什么
线程池是一种基于“池化”思想实现的管理任务和线程的工具。线程过多会带来额外的开销,包括创建、销毁线程的开销、调度线程的开销,同时也降低了计算机的整体性能。线程池维护多个线程,等待分配可并发执行的任务。这种做法,一方面避免了创建、销毁线程的开销,另一方面避免了线程数量过多导致的过分调度问题,保证了对内核的充分利用。
2、使用线程池的收益
1、降低资源消耗
通过池化技术重复利用已创建的线程,降低线程创建和销毁带来的内存和CPU资源开销。
2、提高响应速度
任务到达时,无需等待线程创建即可立即执行。
3、提高线程的可管理性
线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分配导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
4、对线程管理提供更多的扩展性
线程池具备可拓展性,允许创建多种功能不一的线程池。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
3、Java线程池
线程池内部分为任务管理和线程管理两部分,这两部分构成了一个生产者、消费者模型。任务管理部分充当生产者的角色,当任务提交后,线程池会判断该任务后续的流转,(1)直接申请线程执行该任务;(2)缓冲到队列中等待线程执行;(3)拒绝该任务。线程管理部分是消费者,线程被统一维护在线程池内,根据任务请求进行线程的分配,当线程执行完任务后则会继续执行新的任务,最终当线程获取不到任务的时候,线程就会被回收。
3.1、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)
3.1.1、核心参数含义
corePoolSize: 线程池中核心线程的数量。
maximumPoolSize:线程池中最大线程数量。
keepAliveTime:非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长。
unit:keepAliveTime这个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等。
workQueue:线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
threadFactory:为线程池提供创建新线程的功能,可用于设置线程名字等等,一般无须设置该参数。
RejectedExecutionHandler: 拒绝策略,线程池中的线程数量已经达到最大数或者线程池关闭后仍然提交新任务,导致线程池无法执行新任务时,线程池抛出RejectedExecutionException。
3.1.2、任务调度机制(execute)
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();
}
//判断线程池是否为RUNNING状态,并且将任务添加至队列中.
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);
}
//如果不满足上述条件,尝试创建一个非核心线程来执行任务,如果创建失败,调用reject()方法.
else if (!addWorker(command, false))
reject(command);
}
ThreadPoolExecutor 执行execute任务时大致遵循如下流程:
1、如果线程池中的线程数未达到核心线程数,则会立马启用一个核心线程去执行任务。
2、如果线程池中的线程数已经达到核心线程数,但任务队列workQueue未满,则将新任务放入workQueue中等待执行。
3、如果线程池中的线程数已经达到核心线程数,且workQueue已满,但未超过线程池规定最大值,则开启一个非核心线程来执行任务。
4、如果线程池中的线程数已经超过线程池规定最大值,则拒绝执行该任务,采取饱和策略,并抛出RejectedExecutionException异常。
任务调度流程图如下:
3.1.3、任务存储机制-阻塞队列
线程池的本质是对任务和线程的管理,而做到这一点最关键的思想就是将任务和线程两者解耦,不让两者直接关联,才可以做后续的分配工作。线程池中是以生产者消费者模式,通过一个阻塞队列来实现任务的存储,阻塞队列缓存任务,工作线程从阻塞队列中获取任务。
阻塞队列(BlockingQueue)有两个特性:1、在队列为空时,获取元素的线程会等待队列变为非空。2、当队列满时,存储元素的线程会等待队列可用。下图中展示了线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素:
使用不同的阻塞队列可以实现不一样的任务存取策略,下图是常见的阻塞队列以及其特性:
3.1.4、任务申请机制-getTask()
任务的执行有两种可能:一种是任务直接由新创建的线程执行。另一种是线程从任务队列中获取任务然后执行,执行完任务的空闲线程会再次去从队列中申请任务再去执行。第一种情况仅出现在线程初始创建的时候,第二种是线程获取任务是绝大多数的情况。
线程需要从阻塞队列中不断地取任务执行,实现线程管理和任务管理之间的通信,这部分策略由getTask()方法实现,其执行流程如下图所示:
3.1.5、任务拒绝机制-RejectedExecutionHandler
线程池中的线程数目达到maximumPoolSize并且任务缓存队列已满,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。拒绝策略需要实现RejectedExecutionHandler接口来完成:
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
线程池也提供了几种常用的拒绝策略实现:
3.2、几种常用的线程池
FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
参数为核心线程数,只有核心线程,无非核心线程,并且阻塞队列无界
CachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
没有核心线程,只有非核心线程,并且每个非核心线程空闲等待的时间为60s,采用SynchronousQueue队列。 SynchronousQueue不存储元素,如果线程池中有线程空闲,则调用SynchronousQueue的poll方法来移除任务并交给线程处理;如果没有线程空闲,则开启一个新的非核心线程来处理任务。如果线程处理任务速度小于提交任务的速度,则会不断地创建新的线程,这时需要注意不要过度创建,不然线程创建太多会影响性能。
SingleThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
由于只有一个核心线程,当被占用时,其他的任务需要进入队列等待。
ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
4、线程池异常问题解决
常见case:
case1:没有预估好调用的流量级别,核心线程跑满+任务队列存满+非核心线程跑满,导致大量的任务被拒绝执行,大量抛出RejectedExecutionException。
case:队列长度设置过长,请求数量增加时大量任务堆积在队列中,任务执行时间过长,最终导致调用超时。
线程池异常问题基本都是由于参数设置不合理导致,解决方案:
1、正确预估任务量,合理设置核心线程数、总线程数、队列长度。
2、可以将线程池的参数从代码中迁移到分布式配置中心上,实现线程池参数可动态配置和即时生效,缩短解决异常时间。线程池参数动态化前后的参数修改流程对比如下: