解决方案:
自定义线程池的拒绝策略将被拒绝的任务投递到MQ队列【线程池与mq简单结合使用】
一、简介
在我们使用线程池的过程中,经常会遇到超出线程池的缓存队列、超出的最大线程数,这个时候,如果使用的是默认的拒绝策略即直接抛出异常,此时我们的数据就被丢弃了,程序会停止,我们也不知道,因为程序是并发执行的,如果使用了try catch 捕获异常让程序继续执行,但是被拒绝的线程数据已经被丢弃了,今天我们来学习通过自定义线程池的拒绝策略来解决这个问题,可以更加灵活的使用线程池。
报错信息:
rejected from java.util.concurrent.ThreadPoolExecutor@41703c85[Running, pool size = 50, active threads = 50, queued tasks = 10000, completed tasks = 909]
1.1 线程池的参数解释
corePoolSize:线程池中核心线程数的数量
maximumPoolSize:在线程池中允许存在的最大线程数
keepAliveTime:当存在的线程数大于corePoolSize,那么会找到空闲线程去销毁,此参数是设置空闲多久的线程才被销毁。
unit:时间单位
workQueue:工作队列,线程池中的当前线程数大于核心线程的话,那么接下来的任务会放入到队列中
threadFactory:在创建线程的时候,通过工厂模式来生产线程。这个参数就是设置我们自定义的线程创建工厂。
handler:如果超过了最大线程数,那么就会执行我们设置的拒绝策略
1.2 线程池工作流程
当corePoolSize个任务时,来一个任务就创建一个线程。
如果当前线程池的线程数大于了corePoolSize那么接下来再来的任务就会放入到我们上面设置的workQueue队列中。
如果此时workQueue也满了,那么再来任务时,就会新建临时线程,那么此时如果我们设置了keepAliveTime或者设置了allowCoreThreadTimeOut,那么系统就会进行线程的活性检查,一旦超时便销毁线程。
如果此时线程池中的当前线程达到了maximumPoolSize最大线程数,那么就会执行我们刚才设置的handler拒绝策略。
1.3 线程池的四种拒绝策略
AbortPolicy :默认的拒绝策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
DiscardPolicy :如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常。
DiscardOldestPolicy :如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
CallerRunsPolicy:如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。
二、代码实现
2.1 实现Runnable接口
自定义CrmOpneTaskRunable类 实现Runnable接口,并重写run方法。
2.2 创建线程池
private ThreadPoolExecutor threadPoolExecutor(){
return new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveSeconds,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(queueCapacity),
Executors.defaultThreadFactory(),
//TODO 自定义拒绝策略 RejectedExecutionHandler实现rejectedExecution方法
(r, executor) -> {
if (r instanceof CrmOpneTaskRunable) {
CrmOpneTaskRunable crmOpneTaskRunable = (CrmOpneTaskRunable) r;
//直接打印
log.info("线程池队列已满,超出最大线程数,投递队列处理...");
rabbitTemplate.convertAndSend("direct-rejected-policy",crmOpneTaskRunable.getX().toString());
}
}
);
}
通过实现RejectedExecutionHandler
的rejectedExecution
方法来自定义拒绝策略:
我们可以通过Lambda
表达式形式书写。
2.3 使用线程池
使用try catch 包裹一下。
注意:我们在创建Runnable接口的时候使用有参构造将参数传递进去。
因为如果我们在Runnable接口里直接使用@Autowired直接注入restTemplate和crmOrderRepository,在使用时会报空指针异常。
try {
pool.execute(new CrmOpneTaskRunable(x,appId,timestamp,sign,url,restTemplate,crmOrderRepository));
}catch (Exception e){
log.error("开通失败:"+e.getMessage());
}
三、测试
线程池拒绝策略:投递MQ队列:
队列消费: