当前位置: 首页 > news >正文

spring定时器@Scheduled异步调用

文章目录

  • 前言
  • 使用自定义线程池实现异步代码
    • 配置文件
    • 线程池
    • 定时器
    • 输出台结果
    • 解决上一轮定时器任务未执行完成,下一轮就开始执行的问题
  • 使用SchedulingConfigurer实现定时器异步调用
    • 配置文件
    • 定时器类
    • 输出台结果
  • 总结


前言

在springboot中的@schedule默认的线程池中只有一个线程,所以如果在多个方法上加上@schedule的话,此时就会有多个任务加入到延时队列中,因为只有一个线程,所以任务只能被一个一个的执行。
如果有多个定时器,而此时有定时器运行时间过长,就会导致其他的定时器无法正常执行。

代码示例

@Component
public class TestTimer {@Scheduled(cron = "0/1 * *  * * ? ")public void test01(){Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行开始"));try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行结束"));}@Scheduled(cron = "0/1 * *  * * ? ")public void test02(){Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定时任务执行了"));}
}

注意需要在启动类上加上@EnableScheduling
输出台

2024-08-19 19:07:52.010  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:07:52test02定时任务执行了
2024-08-19 19:07:52.010  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:07:52test01定时任务执行开始
2024-08-19 19:07:55.024  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:07:52test01定时任务执行结束
2024-08-19 19:07:55.024  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:07:55test02定时任务执行了
2024-08-19 19:07:56.002  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:07:56test01定时任务执行开始
2024-08-19 19:07:59.016  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:07:56test01定时任务执行结束
2024-08-19 19:07:59.016  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:07:59test02定时任务执行了
2024-08-19 19:08:00.014  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:08:00test01定时任务执行开始
2024-08-19 19:08:03.022  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:08:00test01定时任务执行结束
2024-08-19 19:08:03.022  INFO 10372 --- [   scheduling-1] com.qbh.timer.TestTimer                  : 2024-08-19 19:08:03test02定时任务执行了

从打印的日志也可以看出来,两个定时器共用一个线程。


此时就需要让定时器使用异步的方式进行,以下为实现方式:

使用自定义线程池实现异步代码

配置文件

thread-pool:config:core-size: 8max-size: 16queue-capacity: 64keep-alive-seconds: 180
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** @author qf* @since 2024/08/20*/
@Component
@ConfigurationProperties(prefix = "thread-pool.config")
@Data
public class TestThreadPoolConfig {private Integer coreSize;private Integer maxSize;private Integer queueCapacity;private Integer keepAliveSeconds;
}

线程池

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.RejectedExecutionHandler;/*** @author qf*/
@Configuration
@EnableAsync
public class ThreadPoolConfig {@Autowiredprivate TestThreadPoolConfig testThreadPoolConfig;@Bean(name = "testExecutor")public ThreadPoolTaskExecutor testThreadPoolExecutor() {return getAsyncTaskExecutor("test-Executor-", testThreadPoolConfig.getCoreSize(),testThreadPoolConfig.getMaxSize(), testThreadPoolConfig.getQueueCapacity(),testThreadPoolConfig.getKeepAliveSeconds(), null);}/*** 统一异步线程池** @param threadNamePrefix* @param corePoolSize* @param maxPoolSize* @param queueCapacity* @param keepAliveSeconds* @param rejectedExecutionHandler 拒接策略 没有填null* @return*/private ThreadPoolTaskExecutor getAsyncTaskExecutor(String threadNamePrefix, int corePoolSize, int maxPoolSize, int queueCapacity, int keepAliveSeconds, RejectedExecutionHandler rejectedExecutionHandler) {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(corePoolSize);taskExecutor.setMaxPoolSize(maxPoolSize);taskExecutor.setQueueCapacity(queueCapacity);taskExecutor.setThreadPriority(Thread.MAX_PRIORITY);//线程优先级taskExecutor.setDaemon(false);//是否为守护线程taskExecutor.setKeepAliveSeconds(keepAliveSeconds);taskExecutor.setThreadNamePrefix(threadNamePrefix);taskExecutor.setRejectedExecutionHandler(rejectedExecutionHandler);taskExecutor.initialize();//线程池初始化return taskExecutor;}
}

定时器

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;/*** @author qf*/
@Slf4j
@Component
public class TestTimer {@Async("testExecutor")@Scheduled(cron = "0/1 * *  * * ? ")public void test01() {Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行开始"));try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行结束"));}@Async("testExecutor")@Scheduled(cron = "0/1 * *  * * ? ")public void test02() {Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定时任务执行了"));}
}

输出台结果

2024-08-20 19:33:20.020  INFO 4420 --- [test-Executor-1] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:20test01定时任务执行开始
2024-08-20 19:33:20.020  INFO 4420 --- [test-Executor-2] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:20test02定时任务执行了
2024-08-20 19:33:21.002  INFO 4420 --- [test-Executor-4] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:21test02定时任务执行了
2024-08-20 19:33:21.002  INFO 4420 --- [test-Executor-3] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:21test01定时任务执行开始
2024-08-20 19:33:22.015  INFO 4420 --- [test-Executor-5] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:22test01定时任务执行开始
2024-08-20 19:33:22.015  INFO 4420 --- [test-Executor-6] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:22test02定时任务执行了
2024-08-20 19:33:23.009  INFO 4420 --- [test-Executor-7] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:23test01定时任务执行开始
2024-08-20 19:33:23.009  INFO 4420 --- [test-Executor-8] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:23test02定时任务执行了
2024-08-20 19:33:23.025  INFO 4420 --- [test-Executor-1] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:20test01定时任务执行结束
2024-08-20 19:33:24.003  INFO 4420 --- [test-Executor-3] com.qbh.timer.TestTimer                  : 2024-08-20 19:33:21test01定时任务执行结束

查看输出台可以看出定时器已经异步执行了。但是这里会发现一个问题,可以发现当前定时器任务还没有执行完一轮,下一轮就已经开始了。如果业务中需要用到上一次定时器的结果等情况,则会出现问题。

解决上一轮定时器任务未执行完成,下一轮就开始执行的问题

本人暂时想到的方式是通过加锁的方式,当上一轮未执行完时,下一轮阻塞等待上一轮执行。
改造定时器类

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;/*** @author qf*/
@Slf4j
@Component
public class TestTimer {private final ReentrantLock timerLock = new ReentrantLock();@Async("testExecutor")@Scheduled(cron = "0/1 * *  * * ? ")public void test01() {timerLock.lock();Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行开始"));try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {timerLock.unlock();//释放锁}log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行结束"));}@Async("testExecutor")@Scheduled(cron = "0/1 * *  * * ? ")public void test02() {Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定时任务执行了"));}
}

输出台

2024-08-20 19:55:26.007  INFO 7752 --- [test-Executor-1] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:26test02定时任务执行了
2024-08-20 19:55:26.007  INFO 7752 --- [test-Executor-2] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:26test01定时任务执行开始
2024-08-20 19:55:27.004  INFO 7752 --- [test-Executor-3] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:27test02定时任务执行了
2024-08-20 19:55:28.014  INFO 7752 --- [test-Executor-5] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:28test02定时任务执行了
2024-08-20 19:55:29.009  INFO 7752 --- [test-Executor-2] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:26test01定时任务执行结束
2024-08-20 19:55:29.009  INFO 7752 --- [test-Executor-4] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:29test01定时任务执行开始
2024-08-20 19:55:29.009  INFO 7752 --- [test-Executor-7] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:29test02定时任务执行了
2024-08-20 19:55:30.004  INFO 7752 --- [test-Executor-1] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:30test02定时任务执行了
2024-08-20 19:55:31.015  INFO 7752 --- [test-Executor-5] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:31test02定时任务执行了
2024-08-20 19:55:32.009  INFO 7752 --- [test-Executor-4] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:29test01定时任务执行结束
2024-08-20 19:55:32.009  INFO 7752 --- [test-Executor-7] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:32test02定时任务执行了
2024-08-20 19:55:32.009  INFO 7752 --- [test-Executor-6] com.qbh.timer.TestTimer                  : 2024-08-20 19:55:32test01定时任务执行开始

通过输出台可以看出下一轮的定时器会等待上一轮结束释放锁后才会执行。

使用SchedulingConfigurer实现定时器异步调用

配置文件

import org.springframework.boot.autoconfigure.batch.BatchProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;import java.lang.reflect.Method;
import java.util.concurrent.Executors;@Configuration
public class ScheduleConfig implements SchedulingConfigurer {/*** 计算出带有Scheduled注解的方法数量,如果该数量小于默认池大小(20),则使用默认线程池核心数大小20。* @param taskRegistrar the registrar to be configured.*/@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {Method[] methods = BatchProperties.Job.class.getMethods();int defaultPoolSize = 20;int corePoolSize = 0;if (methods != null && methods.length > 0) {System.out.println(methods.length);for (Method method : methods) {Scheduled annotation = method.getAnnotation(Scheduled.class);if (annotation != null) {corePoolSize++;}}if (defaultPoolSize > corePoolSize) {corePoolSize = defaultPoolSize;}}taskRegistrar.setScheduler(Executors.newScheduledThreadPool(corePoolSize));}
}

定时器类

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;/*** @author qf*/
@Slf4j
@Component
public class TestTimer {@Scheduled(cron = "0/1 * *  * * ? ")public void test01() {Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行开始"));try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test01定时任务执行结束"));}@Scheduled(cron = "0/1 * *  * * ? ")public void test02() {Date date = new Date();log.info((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) + "test02定时任务执行了"));}
}

输出台结果

2024-08-20 20:18:58.002  INFO 24744 --- [pool-2-thread-2] com.qbh.timer.TestTimer                  : 2024-08-20 20:18:58test01定时任务执行开始
2024-08-20 20:18:58.002  INFO 24744 --- [pool-2-thread-1] com.qbh.timer.TestTimer                  : 2024-08-20 20:18:58test02定时任务执行了
2024-08-20 20:18:59.014  INFO 24744 --- [pool-2-thread-1] com.qbh.timer.TestTimer                  : 2024-08-20 20:18:59test02定时任务执行了
2024-08-20 20:19:00.006  INFO 24744 --- [pool-2-thread-3] com.qbh.timer.TestTimer                  : 2024-08-20 20:19:00test02定时任务执行了
2024-08-20 20:19:01.005  INFO 24744 --- [pool-2-thread-1] com.qbh.timer.TestTimer                  : 2024-08-20 20:19:01test02定时任务执行了
2024-08-20 20:19:01.005  INFO 24744 --- [pool-2-thread-2] com.qbh.timer.TestTimer                  : 2024-08-20 20:18:58test01定时任务执行结束
2024-08-20 20:19:02.013  INFO 24744 --- [pool-2-thread-3] com.qbh.timer.TestTimer                  : 2024-08-20 20:19:02test01定时任务执行开始

以上可以看出该方法也可以实现定时器异步执行,并且当上一轮定时器没有执行完时,下一轮会等待上一轮完成后执行。

总结

在Springboot中的@schedule默认的线程池中只有一个线程,当有多个定时器时,只会先执行其中的一个,其他定时器会加入到延时队列中,等待被执行。
Springboot实现定时器异步的方式有

  1. 通过自定义线程池的方式实现异步。
  2. 通过实现SchedulingConfigurer接口的方式实现异步。


http://www.mrgr.cn/news/3759.html

相关文章:

  • 望繁信科技入选2024年第3批上海市高新技术成果转化项目名单
  • AMD E1-1200可以用作nas服务器吗
  • 在Ubuntu16.04里安装ROS Kinetic
  • `HTTP/2` 的服务器推送功能和 `Socket.IO` 对比
  • ## 已解决:`java.sql.SQLSyntaxErrorException: SQL语法错误` 异常的正确解决方法,亲测有效!!! ###
  • DRF——请求的封装与版本管理
  • WPS宏实现一个表格拆分成多个表格的功能
  • 阿里云短信验证码的开通条件、流程
  • chainlit的基本概念聊天对话中的元素
  • golang嵌入图标
  • VIT论文阅读: A Image is Worth 16x16 Words
  • 不同路径
  • 06、stm32 引脚输入
  • 游戏开发设计模式概况
  • 如何在不破产的情况下训练AI模型
  • 开发 LLM 支持的应用程序:Azure 上的 Llama 2(5/n)
  • 算法的学习笔记—把二叉树打印成多行(牛客JZ78)
  • 微服务健康检查:如何通过Eureka实现服务自动剔除与恢复
  • pinctl 和 gpio子系统驱动
  • Nginx高级部分