ThreadPoolExecutor的4种拒绝策略
Java线程池ThreadPoolExecutor 使用一个阻塞队列来进行存储线程任务。
当线程不够用时,则将后续的任务暂存到阻塞队列中,等待有空闲线程来进行。
当这个阻塞队列满了的时候,会出现两种情况
1)正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
2)正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会通过1个策略进行对后续的任务进行处理。
四种策略:
ThreadPoolExecutor.AbortPolicy | ThreadPoolExecutor 默认策略 直接抛出java.util.concurrent.RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 放弃当前任务,并且不会抛出任何异常 |
ThreadPoolExecutor.DiscardOldestPolicy | 会将队列中最早添加的元素移除,再尝试添加,如果失败则按该策略不断重试 |
ThreadPoolExecutor.CallerRunsPolicy | 由调用线程(提交任务的线程)处理该任务,如果调用线程是主线程,那么主线程会调用执行器中的execute方法来执行改任务 |
1、ThreadPoolExecutor.AbortPolicy
线程池的默认拒绝策略为AbortPolicy,即直接抛出RejectedExecutionException异常.
/**
* The default rejected execution handler
*/
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();
//方法的实现
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}直接抛出个RejectedExecutionException异常,也不执行这个任务了
示例:
ThreadPoolExecutor exs = new ThreadPoolExecutor(5, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2));
运行任务名称10时,任务队列满并达到maximumPoolSize时,没有新线程执行任务时,直接抛出异常RejectedExecutionException。
核心线程数(1,2,3,4,5满)->缓存队列数(放2个任务满(任务名称5和6))->最大线程数(6,7,8满)->不执行(任务名称10),抛出异常。
结论:AbortPolicy策略,超过最大线程数后每个线程都抛出RejectedExecutionException

参考执行日志:
---核心线程数:5,线程池中线程数目:1,队列中等待执行的任务数目:0
pool-1-thread-1 在运行任务,任务名称:0
---核心线程数:5,线程池中线程数目:2,队列中等待执行的任务数目:0
---核心线程数:5,线程池中线程数目:3,队列中等待执行的任务数目:0
pool-1-thread-2 在运行任务,任务名称:1
pool-1-thread-3 在运行任务,任务名称:2
---核心线程数:5,线程池中线程数目:4,队列中等待执行的任务数目:0
---核心线程数:5,线程池中线程数目:5,队列中等待执行的任务数目:0
pool-1-thread-4 在运行任务,任务名称:3
---核心线程数:5,线程池中线程数目:5,队列中等待执行的任务数目:1
pool-1-thread-5 在运行任务,任务名称:4
---核心线程数:5,线程池中线程数目:5,队列中等待执行的任务数目:2
---核心线程数:5,线程池中线程数目:6,队列中等待执行的任务数目:2
pool-1-thread-6 在运行任务,任务名称:7
---核心线程数:5,线程池中线程数目:7,队列中等待执行的任务数目:2
---核心线程数:5,线程池中线程数目:8,队列中等待执行的任务数目:2
pool-1-thread-7 在运行任务,任务名称:8
pool-1-thread-8 在运行任务,任务名称:9
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task runnable.Runnable2@2ff4acd0 rejected from java.util.concurrent.ThreadPoolExecutor@54bedef2[Running, pool size = 8, active threads = 8, queued tasks = 2, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at runnable.ThreadPoolDemo2.main(ThreadPoolDemo2.java:30)
pool-1-thread-8 在运行任务,任务名称:5
pool-1-thread-5 在运行任务,任务名称:62.ThreadPoolExecutor.DiscardPolicy
ThreadPoolExecutor.DiscardPolicy:丢弃当前任务,但是不抛出异常。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}示例:
ThreadPoolExecutor exs = new ThreadPoolExecutor(5, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.DiscardPolicy());
运行任务名称10时,任务队列满并达到maximumPoolSize时,没有新线程执行任务时,直接抛弃当前任务:任务名称10。
核心线程数(1,2,3,4,5满)->缓存队列数(放2个任务满(任务名称5和6))->最大线程数(6,7,8满)->不执行(丢弃任务名称10)。
结论:DiscardPolicy策略,大于最大线程数以后的任务完全不执行。

3.ThreadPoolExecutor.DiscardOldestPolicy
ThreadPoolExecutor.DiscardOldestPolicy:丢弃位于工作队列头部的任务(最旧任务)。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}示例:
ThreadPoolExecutor exs = new ThreadPoolExecutor(5, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.DiscardOldestPolicy());
运行任务名称10时,任务队列满并达到maximumPoolSize时,没有新线程执行任务时,直接抛弃队列种最早的任务:任务名称5。
核心线程数(1,2,3,4,5满)->缓存队列数(放2个任务满(任务名称5和6))->最大线程数(6,7,8满)->不执行(丢弃最早任务名称5)。
结论:DiscardOldestPolicy策略,超出最大线程数后的任务将顶替缓存队列中最早的任务。不抛异常但是被顶替掉的任务将丢失不执行。

4.ThreadPoolExecutor.CallerRunsPolicy
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}示例:
ThreadPoolExecutor exs = new ThreadPoolExecutor(5, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),new ThreadPoolExecutor.CallerRunsPolicy());
运行任务名称10时,任务队列满并达到maximumPoolSize时,没有新线程执行任务,由调用线程(这里是主线程)处理该任务。
核心线程数(1,2,3,4,5满)->缓存队列数(放2个任务满(任务名称5和6))->最大线程数(6,7,8满)->由主线程执行任务名称10.
结论:CallerRunsPolicy策略是不会丢失任何任务或者抛出任何异常的, 适应于对数据要求比较严谨的任务。
CallerRunsPolicy策略的缺点就是可能会阻塞主线程。
(main线程调用线程池执行任务,线程池处理不了的就退回给main线程执行。这个不会异常也不会丢弃任务,但是并不一定是最好的,因为"线程池一定是竭尽所能去执行任务的",所以如果执行不了,很大可能是此时高并发量非常大,强行让main执行很可能导致OOM之类的问题击穿整个系统,所以慎用这个。)

策略总结:
keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。
AbortPolicy策略的测试代码示例:
package runnable;
import java.util.concurrent.*;
//定义一个 Runnable接口的实现类
class Runnable2 implements Runnable{
String taskName;
public Runnable2(String taskName){
this.taskName=taskName;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+" 在运行任务,任务名称:"+taskName);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadPoolDemo2 {
public static void main(String[] args){
//int threadNum=8;
//创建一个自定义线程池对象
ThreadPoolExecutor exs = new ThreadPoolExecutor(5, 8, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2));
for(int i=0;i<11;i++){
//通过 ExecutorService 对象的 execute 方法将任务添加到线程去执行
exs.execute(new Runnable2(String.valueOf(i)));
System.out.println("---核心线程数:" + exs.getCorePoolSize()+",线程池中线程数目:"+exs.getPoolSize()+",队列中等待执行的任务数目:"+ exs.getQueue().size());
//System.out.println("---线程池中线程数目:"+exs.getPoolSize());
//System.out.println("---队列中等待执行的任务数目:"+ exs.getQueue().size());
}
exs.shutdown();
}
}
参考:四种线程池拒绝策略