一.线程池的拒绝策略在什么时候启用
线程池:
//参数最多的构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
参数解释:
1 corePoolSize:线程池核心大小
2 maximumPoolSize:最大线程池大小
3 keepAliveTime和unit合在一起用于指定线程池中空闲线程的最大存活时间。
4 workQueue:工作队列
5 threaaadFactory: 用于创建工作者线程的线程工厂
6 handler: 线程池饱和时,封装被拒绝任务的处理策略。
抽象方法:abstract void rejectedExecution(Runnable r,ThreadPoolExecutor executor);
r代表被拒绝的任务,executor代表拒绝任务r的线程池实例。
我们来了解一下线程池执行任务的基本过程:
1.每过来一个任务,就启动一个核心线程,去执行任务
2.如果核心线程都用完了,再过来任务,存入阻塞队列
3.如果阻塞队列也存满了,再过来任务,可以再启动线程,直到达到最大线程数量
4.如果达到了最大线程数量,再过来任务,启动拒绝策略。
由此可知,任务数量达到了最大线程池数量,并且再过来任务时会启动拒绝策略。
二.拒绝策略的操作说明
1. 当客户端提交的任务被拒绝时,线程池所关联的RejectedExecutionHandler的
rejectedExecution方法会被线程池调用。
2.ThreadPoolExecutor自身提供了几个现成的RejectedExecutionHandler
接口的实现类。其中ThreadPoolExecutor.AbortPolicy是
ThreadPoolExecutor使用的默认RejectedExecutionHandler。
3. 如果默认的RejectedExecutionHandler(它会直接抛出异常)无法满足要求,
那么我们可以优先考虑ThreadPoolExecutor自身提供的其他
RejectedExecutionHandler,其次才去考虑使用自身实现的
RejectedExecutionHandler接口。
其他实现类的表现如下所示:
实现类 所实现的处理策略
ThreadPoolExecutor.AbortPolicy 直接抛出异常
ThreadPoolExecutor.DiscardPolicy 丢弃当前被拒绝的任务(而不抛出任何异常)
ThreadPoolExecutor.DiscardOldestPolicy 将工作队列中最老的任务丢弃,然后重新尝试 接纳被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy 在客户端线程中执行被拒绝的任务
三.四种拒绝策略的举例说明
1. ThreadPoolExecutor.AbortPolicy
测试代码示意图:在测试类中写了两个线程r和r1,r线程run方法中定义了线程的一个睡眠时间为5000毫秒,执行完后输出一行分割线,r1线程的run方法中获取的线程的名称,与字符串“aaaaaa”拼接打印。创建了线程池的核心线程池数量为1,最大线程池数量为2,线程池中空闲线程的最大存活时间为2秒,工作队列为new ArrayBlockingQueue<Runnable>(1), 线程工厂为Executors.defaultThreadFactory(), 这里的拒绝策略为:AbortPolicy()。
总共向线程池提交了四次任务,前三次为 任务为执行r线程,第四次执行r1线程,从运行结果我们可以看出,发生了异常,分割线只打印了三次,并没有打印我们r1线程的打印语句。这就是AbortPolicy()拒绝策略的处理方式,这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException的RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略,就是如下图所示的异常。
2.ThreadPoolExecutor.DiscardPolicy
测试代码示意图:
这里代码和上图一样,只是把拒绝策略修改为DiscardPolicy(),我们看一下打印结果
从结果我们看见程序已经执行结束,打印了三次分割线,执行了r线程,但是没有抛出异常,也没有输出r1线程的输出语句。这就是DiscardPolicy()拒绝策略的处理方式,这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
3.ThreadPoolExecutor.DiscardOldestPolicy
测试代码示意图:
这里代码和AbortPolicy()图一样,只是把拒绝策略修改为DiscardOldestPolicy(),我们看一下打印结果
从结果我们看见程序已经执行结束,打印了两次分割线,执行了两次r线程,但是却输出了r1线程的输出语句pool-1-thread-2aaaaaa,没有抛出异常。这就是DiscardOldestPolicy()拒绝策略的处理方式,如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。
4.ThreadPoolExecutor.CallerRunsPolicy
测试代码示意图:
这里代码和AbortPolicy()图一样,只是把拒绝策略修改为CallerRunsPolicy();,我们看一下打印结果
从结果我们看见程序已经执行结束,首先输出了r1线程的输出语句,但是我们发现此时r1线程的名称是mainaaaaaa,并不是pool...,下面输出了三次r线程的输出语句。这就是CallerRunsPolicy()拒绝策略的处理方式,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,相当于当线程池无能力处理当前任务时,会将这个任务的执行权交予提交任务的线程来执行,也就是谁提交谁负责,从而降低新任务的流量。(谁调用了你,到达最大线程数时,你回去找调用你的人,然后听从调用你的人安排)(超出的我们能办的给你办,不能办的给你回退 )这样的话提交的任务就不会被丢弃而造成业务损失,如果任务比较耗时,那么这段时间内提交任务的线程也会处于忙碌状态而无法继续提交任务,这样也就减缓了任务的提交速度,这相当于一个负反馈,也有利于线程池中的线程来消化任务。这种策略算是最完善的相对于其他三个。
我们再来说明一下线程池默认使用的拒绝策略:
测试代码示意图:这里我使用了线程池创建的只有五个参数的构造方法,其中并没有定义拒绝策略。
这里我们发现,当我们的任务数量达到最大线程池数量时,程序抛出了异常,这就体现了拒绝策略中AbortPolicy()方式的拒绝策略。
我们从底层底层源代码也可以看出默认的拒绝策略为AbortPolicy():
四.总结
四种线程池拒绝策略,具体使用哪种策略,还得根据实际业务场景才能做出抉择。