@EnableScheduling 和 @Scheduled 实现定时任务的任务延期问题

前言

在复盘 ieg 一面看到定时任务阻塞的问题时,研究了下 @EnableScheduling 的源码,觉得可以单开一篇文章讲一讲

本文主要讲述了使用 @EnableScheduling 可能出现的线程阻塞导致定时任务延期的问题,也顺便解释了动态定时任务源码上的实现

引用文章:

@Schedule定时任务+分布式环境:@Schedule定时任务+分布式环境,这些坑你一定得注意!!! (qq.com)

java 中的线程池参数:java中四种线程池及poolSize、corePoolSize、maximumPoolSize_maximum-pool-size-CSDN博客

线程池的拒绝策略:线程池的RejectedExecutionHandler(拒绝策略)-CSDN博客

Java 中实现定时任务:Java中实现定时任务,有多少种解决方案?好久没更新博客了,最近上班做了点小东西,总结复盘一下。主要介绍了定时任务的三种 - 掘金 (juejin.cn)

线程阻塞问题

问题根源

Java中 使用 Springboot 自带的定时任务 @EnableScheduling 和 @Scheduled 注解,会装配一个 SchedulingConfiguration 的类

@Target(ElementType.TYPE)  
@Retention(RetentionPolicy.RUNTIME)  
@Import(SchedulingConfiguration.class)  
@Documented  
public @interface EnableScheduling {  }
@Configuration(proxyBeanMethods = false)  
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)  
public class SchedulingConfiguration {  @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)  public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {  return new ScheduledAnnotationBeanPostProcessor();  }  
}

这个配置类又会创建一个 ScheduledAnnotationBeanPostProcessor 的 Bean

在这个类的无参构造中又初始化了一个 ScheduledTaskRegistrar 的对象

public ScheduledAnnotationBeanPostProcessor() {  this.registrar = new ScheduledTaskRegistrar();  
}

在 创建单例或刷新上下文之后,会执行 finishRegistration 方法,最后执行 registrar 的 afterPropertiesSet 方法:

@Override  
public void afterSingletonsInstantiated() {  // Remove resolved singleton classes from cache  this.nonAnnotatedClasses.clear();  if (this.applicationContext == null) {  // Not running in an ApplicationContext -> register tasks early...  finishRegistration();  }  
}  @Override  
public void onApplicationEvent(ContextRefreshedEvent event) {  if (event.getApplicationContext() == this.applicationContext) {  // Running in an ApplicationContext -> register tasks this late...  // giving other ContextRefreshedEvent listeners a chance to perform  // their work at the same time (e.g. Spring Batch's job registration).  finishRegistration();  }  
}  private void finishRegistration() {  if (this.scheduler != null) {  this.registrar.setScheduler(this.scheduler);  }  // ...this.registrar.afterPropertiesSet();  
}

ScheduledTaskRegistrar 的成员变量包括任务的执行器以及几种类型的定时任务列表

@Nullable  
private TaskScheduler taskScheduler;  @Nullable  
private ScheduledExecutorService localExecutor;  @Nullable  
private List<TriggerTask> triggerTasks;  @Nullable  
private List<CronTask> cronTasks;

afterPropertiesSet 方法会获取一个执行器

@Override  
public void afterPropertiesSet() {  scheduleTasks();  
}  /**  
* Schedule all registered tasks against the underlying  
* {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.  
*/  
@SuppressWarnings("deprecation")  
protected void scheduleTasks() {  if (this.taskScheduler == null) {  this.localExecutor = Executors.newSingleThreadScheduledExecutor();  this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);  }  if (this.triggerTasks != null) {  for (TriggerTask task : this.triggerTasks) {  addScheduledTask(scheduleTriggerTask(task));  }  }  if (this.cronTasks != null) {  for (CronTask task : this.cronTasks) {  addScheduledTask(scheduleCronTask(task));  }  }  if (this.fixedRateTasks != null) {  for (IntervalTask task : this.fixedRateTasks) {  addScheduledTask(scheduleFixedRateTask(task));  }  }  if (this.fixedDelayTasks != null) {  for (IntervalTask task : this.fixedDelayTasks) {  addScheduledTask(scheduleFixedDelayTask(task));  }  }  
}

进入 newSingleThreadScheduledExecutor 可以看到,默认使用了一个 corePoolSize 为 1, maximumPoolSize 为 Integer.MAX_VALUE 的线程池

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {  return new DelegatedScheduledExecutorService  (new ScheduledThreadPoolExecutor(1));  
}
public ScheduledThreadPoolExecutor(int corePoolSize) {  super(corePoolSize, Integer.MAX_VALUE,  DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,  new DelayedWorkQueue());  
}
public ThreadPoolExecutor(int corePoolSize,  int maximumPoolSize,  long keepAliveTime,  TimeUnit unit,  BlockingQueue<Runnable> workQueue) {  this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  Executors.defaultThreadFactory(), defaultHandler);  
}

而线程池主要有几个重要的参数分别是:

  1. corePoolSize:线程池的基本大小。
  2. maximumPoolSize:线程池中允许的最大线程数。
  3. poolSize:线程池中当前线程的数量。

当提交一个新任务时,若

  1. poolSize < corePoolSize : 创建新线程处理该任务
  2. poolSize = corePoolSize : 将任务置于阻塞队列中
  3. 阻塞队列的容量达到上限,且这时 poolSize < maximumPoolSize :
  4. 阻塞队列满了,且 poolSize = maximumPoolSize : 那么线程池已经达到极限,会根据饱和策略 RejectedExecutionHandler 拒绝新的任务,默认是 AbortPolicy 会丢掉任务并抛出异常

解决方案

注入自己编写的线程池,自行设置参数:

@Configuration  public class MyTheadPoolConfig {  @Bean  public TaskExecutor taskExecutor() {  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  //设置核心线程数  executor.setCorePoolSize(10);  //设置最大线程数  executor.setMaxPoolSize(20);  //缓冲队列200:用来缓冲执行任务的队列  executor.setQueueCapacity(200);  //线程活路时间 60 秒  executor.setKeepAliveSeconds(60);  //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池  // 这里我继续沿用 scheduling 默认的线程名前缀  executor.setThreadNamePrefix("nzc-create-scheduling-");  //设置拒绝策略  executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());  executor.setWaitForTasksToCompleteOnShutdown(true);  return executor;  }  }

在定时任务的类上再加一个 @EnableAsync 注解,给方法添加一个 @Async 即可

@Slf4j  
@Component  
@EnableAsync  
@EnableScheduling  
public class ScheduleService {  @Autowired  TaskExecutor taskExecutor;  @Async(value = "taskExecutor")  @Scheduled(cron = "0/5 * * * * ? ")  public void testSchedule() {  try {  Thread.sleep(10000);  log.info("当前执行任务的线程号ID===>{}", Thread.currentThread().getId());  } catch (Exception e) {  e.printStackTrace();  }   }  
}

动态定时任务

上面提到了

@EnableScheduling 导入了 SchedulingConfiguration,SchedulingConfiguration 又创建了 ScheduledAnnotationBeanPostProcessor 的Bean,ScheduledAnnotationBeanPostProcessor 又实例化了 ScheduledTaskRegistrar 对象,即

@EnableScheduling -> SchedulingConfiguration -> ScheduledAnnotationBeanPostProcessor -> ScheduledTaskRegistrar

实际上,在 ScheduledAnnotationBeanPostProcessor 的 finishRegistration 方法中,会先获取所有实现了 SchedulingConfigurer 接口的 Bean,并执行他们的 configureTasks 方法


private void finishRegistration() {  if (this.scheduler != null) {  this.registrar.setScheduler(this.scheduler);  }  if (this.beanFactory instanceof ListableBeanFactory) {  Map<String, SchedulingConfigurer> beans =  ((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);  List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());  AnnotationAwareOrderComparator.sort(configurers);  for (SchedulingConfigurer configurer : configurers) {  configurer.configureTasks(this.registrar);  }  }// ...
}

我们可以通过配置一个实现了 SchedulingConfigurer 接口的 Bean,实现动态加载定时任务的执行时间

@Data  
@Slf4j  
@Component  
@RequiredArgsConstructor  
@PropertySource("classpath:task-config.ini")  
public class ScheduleTask implements SchedulingConfigurer {  // private Long timer = 100 * 1000L;  @Value("${printTime.cron}")  private String cron;  @Override  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {  // 间隔触发的任务  taskRegistrar.addTriggerTask(new Runnable() {  @Override  public void run(){  // ...}}, new Trigger() {  @Override  public Date nextExecutionTime(TriggerContext triggerContext) {  // 使用CronTrigger触发器,可动态修改cron表达式来操作循环规则  CronTrigger cronTrigger = new CronTrigger(cron);  Date nextExecutionTime = cronTrigger.nextExecutionTime(triggerContext);  return nextExecutionTime;  // 使用PerodicTrigger触发器,修改timer变量指定操作间隔,单位为毫秒// PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);  // Date nextExecutionTime = periodicTrigger.nextExecutionTime(triggerContext);  // return nextExecutionTime;  }  });  }  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/53373.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot3核心特性-核心原理

目录 传送门前言一、事件和监听器1、生命周期监听2、事件触发时机 二、自动配置原理1、入门理解1.1、自动配置流程1.2、SPI机制1.3、功能开关 2、进阶理解2.1、 SpringBootApplication2.2、 完整启动加载流程 三、自定义starter1、业务代码2、基本抽取3、使用EnableXxx机制4、完…

zynq的PS端mac与RTL8211F的连接要点

目录 1 VCCO_MIO12 PS_MIO_VREF3 PS的引脚4 RXDLY TXDLY5 ZYNQ的MAC可以调整延时吗 1 VCCO_MIO1 接1.8V 2 PS_MIO_VREF 接0.9V&#xff0c;可通过电阻分压 可通过电阻分压 3 PS的引脚 4 RXDLY TXDLY RXDLY RXD[0] TXDLY RXD[1] 与XC7Z020的PS端MAC连接&#xff0c;必须…

CVE-2024-2389 未经身份验证的命令注入

什么是 Progress Flowmon? Progress Flowmon 是一种网络监控和分析工具,可提供对网络流量、性能和安全性的全面洞察。Flowmon 将 Nette PHP 框架用于其 Web 应用程序。 未经身份验证的路由 我们开始在“AllowedModulesDecider.php”文件中枚举未经身份验证的端点,这是一个描…

1.pytest基础知识(默认的测试用例的规则以及基础应用)

一、pytest单元测试框架 1&#xff09;什么是单元测试框架 单元测试是指再软件开发当中&#xff0c;针对软件的最小单位&#xff08;函数&#xff0c;方法&#xff09;进行正确性的检查测试。 2&#xff09;单元测试框架 java&#xff1a;junit和testing python&#xff1a;un…

arcgisPro地理配准

1、添加图像 2、在【影像】选项卡中&#xff0c;点击【地理配准】 3、 点击添加控制点 4、选择影像左上角格点&#xff0c;然后右击填入目标点的投影坐标 5、依次输入四个格角点的坐标 6、点击【变换】按钮&#xff0c;选择【一阶多项式&#xff08;仿射&#xff09;】变换 7…

基于SpringBoot+定时任务实现地图上绘制车辆实时运动轨迹图

目录 1. 项目结构 2. Maven依赖配置 (pom.xml) 3. 实现后端服务 4. 配置文件 (application.properties) 5. 启动项目 6. 访问页面 实现基于北斗卫星的车辆定位和轨迹图的Maven工程&#xff08;使用模拟数据&#xff09;&#xff0c;我们将使用以下技术&#xff1a; Spri…

使用c#制作一个小型桌面程序

封装dll 首先使用visual stdio 创建Dll新项目,然后属性管理器导入自己的工程属性表&#xff08;如果没有可以参考visual stdio 如何配置opencv等其他环境&#xff09; 创建完成后 系统会自动生成一些文件&#xff0c;其中 pch.cpp 先不要修改&#xff0c;pch.h中先导入自己需…

蓝牙模块—BLE-CC41-A

1. 蓝牙的特点 蓝牙模块采用的 TI 公司设计的 CC2541芯片&#xff0c;主要面向低功耗蓝牙通信方案&#xff0c;该模块的工作频段为 2.4Ghz&#xff0c;这个频段属于国际通用频段 注意&#xff1a;蓝牙集成了一个状态指示灯&#xff0c;LED灯如果均匀慢速闪烁&#xff0c;就表示…

【渗透测试】-vulnhub源码框架漏洞-Os-hackNos-1

vulnhub源码框架漏洞中的CVE-2018-7600-Drupal 7.57 文章目录  前言 1.靶场搭建&#xff1a; 2.信息搜集&#xff1a; 主机探测&#xff1a; 端口扫描&#xff1a; 目录扫描&#xff1a; 3.分析&#xff1a; 4.步骤&#xff1a; 1.下载CVE-2018-7600的exp 2.执行exp: 3.写入木…

Docker安装rabbitmq并配置延迟队列

下载rabbitmq镜像 docker pull rabbitmq:management 运行rabbitmq镜像 docker run -id --namerabbitmq -p 5671:5671 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 -e RABBITMQ_DEFAULT_USERtom -e RABBITMQ_DEFAULT_PASStom rabbitmq:management …

idea上传jar包到nexus

注意&#xff1a;确保idea中项目为maven项目&#xff0c;并且在nexus中已经创建了maven私服。 1、配置pom.xml中推送代码配置 <distributionManagement> <repository> <id>releases</id> <url>http://127.0.0.1:8001/repository/myRelease/<…

Mybatis自定义TypeHandler,直接存储枚举类对象

在这篇文章中&#xff0c;我们已经知道如何使用枚举类直接接受前端的数字类型参数&#xff0c;省去了麻烦的转换。如果数据库需要保存枚举类的code&#xff0c;一般做法也是代码中手动转换&#xff0c;那么能不能通过某种机制&#xff0c;省去转换&#xff0c;达到代码中直接保…

【Unity-UGUI组件拓展】| Image 组件拓展,支持FIlled和Slice功能并存

🎬【Unity-UGUI组件拓展】| Image 组件拓展,支持FIlled和Slice功能并存一、组件介绍二、组件拓展方法三、完整代码💯总结🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏…

博睿谷IT认证-订阅试学习

在这个信息爆炸的时代&#xff0c;拥有一张IT认证证书&#xff0c;就像拿到了职场晋升的通行证。博睿谷&#xff0c;作为IT认证培训的佼佼者&#xff0c;帮你轻松拿下华为、Oracle等热门认证。下面&#xff0c;让我们一起看看博睿谷如何助你一臂之力。 学习时间&#xff0c;你说…

将阮一峰老师的《ES6入门教程》的源码拷贝本地运行和发布

你好同学&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 阮一峰老师的《ES6入门教程》应该是很多同学学习 ES6 知识的重要参考吧&#xff0c;应该也有很多同学在看该文档的时候&#xff0c;想知道这个教程的前端源码是怎么实现的&#xff0c;也可能有同学下载…

移动技术开发:ListView水果列表

1 实验名称 ListView水果列表 2 实验目的 掌握自定义ListView控件的实现方法 3 实验源代码 布局文件代码&#xff1a; activity_main.xml: <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.androi…

远程连接MySQL并操作

配置MySQL开发环境 如果你使用的是基于Debian的系统&#xff08;如Ubuntu&#xff09;&#xff0c;可以在终端通过如下步骤安装MySQL开发包。 更新软件包列表 运行以下命令以确保你拥有最新的软件包列表。 sudo apt-get update安装libmysqlclient-dev开发包 执行以下命令以…

【Java注解】

Java注解&#xff08;Annotation&#xff09;让其他程序根据注解信息来决定怎么执行该程序。 是Java 5中引入的一种特殊类型的注释&#xff0c;它可以被用来为代码添加元数据&#xff0c;即关于代码的数据。 注解不会改变程序的逻辑&#xff0c;但它们可以被其他程序使用&…

Datawhale X 南瓜书 task02学习笔记

算法原理引入 样本点通常应该在模型的2侧&#xff0c;原因&#xff1a;在实际中&#xff0c;因为某种不可控的因素&#xff0c;测出来的样本点肯定是有误差的。如果样本数据点都在模型上&#xff0c;则说明在建立模型时&#xff0c;把误差也考虑进去了&#xff0c;这就是我们说…

【技术解析】消息中间件MQ:从原理到RabbitMQ实战(深入浅出)

文章目录 【技术解析】消息中间件MQ&#xff1a;从原理到RabbitMQ实战(深入浅出)1.简介1.1 什么是消息中间件1.2 传统的http请求存在那些缺点1.3 Mq应用场景有那些1.4 为什么需要使用mq1.5 Mq与多线程之间区别1.6 Mq消息中间件名词1.7主流mq区别对比1.8 Mq设计基础知识 2.Rabbi…