这是我参与更文挑战的第8天,活动详情查看:更文挑战
一. 线程池的概念:
线程池就是管理线程的池子,首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
线程池的优势
通过重用已存在的线程,降低线程创建和销毁线程造成的开销,
提高系统响应速度,当有任务到达时,直接从线程池中取线程,通过复用已存在的线程,无需等待便能立即执行;
提高线程可管理性。因为线程若是无限制的创建,不仅会消耗系统资源,还会降低系统稳定性。(内存占用过多而产生OOM,并且会造成cpu过度切换)
二.Java线程池创建与主要参数
无论是创建何种类型线程池(FixedThreadPool、CachedThreadPool…),均会调用ThreadPoolExecutor构造函数。
newFixedThreadPool (固定数目线程的线程池)
newCachedThreadPool(可缓存线程的线程池)
newSingleThreadExecutor(单线程的线程池)
newScheduledThreadPool(定时及周期执行的线程池)
public ThreadPoolExecutor(
int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
int maximumPoolSize, // 线程数的上限
long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,
// 超过这个时间,多余的线程会被回收。
BlockingQueue<Runnable> workQueue, // 任务的排队队列
ThreadFactory threadFactory, // 新线程的产生方式
RejectedExecutionHandler handler) // 拒绝策略
复制代码
corePoolSize:the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set (核心线程数大小:不管它们创建以后是不是空闲的。线程池需要保持 corePoolSize 数量的线程,除非设置了 allowCoreThreadTimeOut。)
maximumPoolSize:the maximum number of threads to allow in the pool。(最大线程数:线程池中最多允许创建 maximumPoolSize 个线程。)
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。(存活时间:如果经过 keepAliveTime 时间后,超过核心线程数的线程还没有接受到新的任务,那就回收。)
unit:the time unit for the {@code keepAliveTime} argument (keepAliveTime 的时间单位。)
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。(存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在这里。它仅仅用来存放被 execute 方法提交的 Runnable 任务。所以这里就不要翻译为工作队列了,好吗?不要自己给自己挖坑。)
threadFactory:the factory to use when the executor creates a new thread。(线程工程:用来创建线程工厂。比如这里面可以自定义线程名称,当进行虚拟机栈分析时,看着名字就知道这个线程是哪里来的,不会懵逼。)
handler :the handler to use when execution is blocked because the thread bounds and queue capacities are reached。(拒绝策略:当队列里面放满了任务、最大线程数的线程都在工作时,这时继续提交的任务线程池就处理不了,应该执行怎么样的拒绝策略。)
三.线程池任务执行过程:
提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
四.线程池拒绝策略
线程池拒绝策略分为一下几种:
AbortPolicy:直接抛出RejectedExecutionException,默认策略。
DiscardPolicy: 什么也不做,直接丢弃任务。
DiscardOldestPolicy:抛弃下一个将要被执行的任务(丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置)。
CallerRunsPolicy:主线程中执行任务(由提交任务者执行这个任务)。
五.工作队列
几种典型的工作队列
ArrayBlockingQueue:使用数组实现的有界阻塞队列,特性先进先出。
LinkedBlockingQueue:LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列;
PriorityBlockingQueue:使用平衡二叉树堆,实现的具有优先级的无界阻塞队列
DelayQueue:DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序;
newScheduledThreadPool线程池使用了这个队列;
SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列;
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。 相对于其他阻塞队列LinkedTransferQueue多了tryTransfer和transfer方法。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。 是一个由链表结构组成的双向阻塞队列
六.常见线程池
SingleThreadExecutor:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
创建单个线程。它适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1,每次只能有一个线程存活。使用无界队列LinkedBlockingQueue(先进先出原则,所以保证了任务的按顺序逐一进行)作为线程池的工作队列。
当线程池中没有线程时,会创建一个新线程来执行任务。
当前线程池中有一个线程后,将新任务加入LinkedBlockingQueue
线程执行完第一个任务后,会在一个无限循环中反复从LinkedBlockingQueue 获取任务来执行。
keepAliveTime为0
使用场景:适用于串行执行任务的场景,一个任务一个任务地执行;
FixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
复制代码
特点:
核心线程数和最大线程数大小一样
keepAliveTime为0L,代表多余的线程立刻终止
阻塞队列为无界队列LinkedBlockingQueue
FixedThreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。
注意:使用无界队列的线程池会导致内存飙升,因为newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长,会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致OOM;
使用场景:适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
CachedThreadPool:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
复制代码
特点:
核心线程数为0
全部都是非核心线程,最大线程数为Integer.MAX_VALUE
阻塞队列是SynchronousQueue(最多只能存在一个元素,有新的任务则阻塞等待)
非核心线程空闲存活时间为60秒
使用场景:执行大量短生命周期任务。因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程;每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务。
ScheduledThreadPool:
线程总数阈值为Integer.MAX_VALUE,工作队列使用DelayedWorkQueue,
非核心线程存活时间为0,所以线程池仅仅包含固定数目的核心线程。
两种方式提交任务:
scheduleAtFixedRate: 按照固定速率周期执行
scheduleWithFixedDelay:上个任务延迟固定时间后执行
使用场景:周期性执行任务,并且需要限制线程数量的场景