一、线程池的优势
1.减少资源消耗 :通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.方便管理:对线程进行统一的分配,监控和调优。
二、线程池运行流程
1. 可能大家已经了解了线程池的参数,但是对线程池整体的运行流程还不是很清晰,下面我将利用去银行取款的场景来模拟线程池的运行,本文的核心,希望仔细阅读,必有收获。
2. 参数对照
所有的柜台 = 线程池
核心线程数 = 所有普通银行柜员
最大线程数 = 银行大厅经理
队列 = 银行的座位
保安 = 拒绝策略
经理观察时间 = keepAliveTime
时间单位 = unit
3. 运行流程介绍
step1 : 假设有一天你去银行取钱,正常情况我们就去柜台办理。
类比程序:相当于,一个任务来了,我们直接交给核心线程去处理。
step2: 假设我们看到所有柜员都在在办理业务,那我们只能在座位上等待。
类比程序:相当于,核心线程数满了,任务就被放到队列里面去了。
step3: 假设所有座位也坐满了,这时候大厅经理发现人太多了,他说我开一个vip窗口吧,我也帮忙处理一下,然后叫座位上的人来他的vip窗口办理业务。
类比程序:相当于核心线程数满了,队列也满了,那我们就把任务放到最大线程数里面。
step4: 假设所有柜台也有人,座位也满了,vip窗口也在办理,保安发现在大厅外还有人想进来办理业务,这时候保安说我得保证大厅内正常的运行,外面人不允许进来了。
类比程序:相当于核心线程数满了,队列也满了,最大线程数也满了,那么我们就执行拒绝策略。
step5: 假设办理高峰期过了,经理发现,座位已经空出来了,正常的柜员办理业务就够用了,于是他决定在vip柜台观察10分钟,如果没有人没有把座位再次坐满的话,他就有撤销vip窗口了。
类似程序:相当于队列已经不满了,最大线程数已经停止工作了,并且等待了10(keepAliveTime)分钟(unit), 都没有高峰期了,那么线程池将收回最大线程数,保留核心线程数。
三、线程池的参数
/**
* 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:
* {@code corePoolSize < 0}
* {@code keepAliveTime < 0}
* {@code maximumPoolSize <= 0}
* {@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 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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
四、参数详解
1.任务队列(workQueue):
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。
2.拒绝策略(handler)
AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
3.线程工厂(threadFactory)
默认已帮我们实现,但是工作中一般我们用于自定义定义线程池的名字,由于项目中存在很多线程池,定义特殊含义的名字可以帮助我们在生产中快速的查找日志,识别哪个线程池出了异常。
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 100, 1,
TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000),
new CustomizableThreadFactory("thread-demo-"),
new ThreadPoolExecutor.CallerRunsPolicy()
);