Java | 一分钟掌握定时任务 | 5 - Spring Task

news2024/11/24 11:50:40

作者:Mars酱

声明:本文章由Mars酱原创,部分内容来源于网络,如有疑问请联系本人。

转载:欢迎转载,转载前先请联系我!

前言

多线程解决了并发阻塞问题,但是不能方便的表达我们的定时方式,目前单体架构定时任务用的多的就应该是Spring Task中的注解方式了吧?

@Scheduled

scheduled注解常用的几个:

cron:支持灵活的cron表达式

fixedRate:固定频率。比如:2号线地铁每5分钟一趟,那么2号线的所有列车其实已经安排好了时刻表,所以每台准点发就行了,但是如果其中一趟晚点,那么下一趟就会延迟。

fixedDelay:固定时延。它的意思是表示上个任务结束,到下个任务开始的时间间隔。无论任务执行花费多少时间,两个任务间的间隔始终是一致的。

搞一搞

@Scheduled(fixedDelay = 1000 * 5)
public void timerTaskA(){
    // Mars酱 做业务a...
}

每间隔5秒执行一次

@Scheduled(cron = "0 0 1 * * ? ")
public void timerTaskB(){
    // Mars酱 做业务b...
}

这是支持cron表达式的,每天凌晨1点执行

Scheduled会阻塞吗?

我们来分析下Spring的源代码吧,如果我们用fixedRate或者fixedDelay,可以在 Spring 的@Scheduled的源代码实现部分找到如下代码:

会在一个registrar对象中添加注解相对应的对象,这个registrar是ScheduledTaskRegistrar对象:

private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();

而这个ScheduledTaskRegistrar对象中有一个ScheduledExecutorService属性:

	@Nullable
	private ScheduledExecutorService localExecutor;

这个就是我们上篇中提到的多线程定时任务实现,继续在在ScheduledTaskRegistrar中又找到创建这个对象的方法:

	/**
	 * 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));
			}
		}
	}

这里第一个if判断就是创建那个localExecutor对象,使用的是newSingleThreadScheduledExecutor。在 Java | 一分钟掌握异步编程 | 3 - 线程异步 - 掘金 (juejin.cn)中提到过,这是创建单线程的线程池方式。那么一个单线程去跑多个定时任务是不是就会产生阻塞?来证明一下。

改写一下之前的例子,两个都是5秒执行,其中一个任务在执行的时候再延迟10秒,看是不是会影响到另一个线程的定时任务执行。改写后的代码如下:

import org.springframework.boot.SpringApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.Date;

/**
 * @author mars酱
 */
@EnableScheduling
public class MarsSpringScheduled {
    public static void main(String[] args) {
        SpringApplication.run(MarsSpringScheduled.class, args);
    }

    @Scheduled(fixedDelay = 5000)
    public void timerTaskA() throws InterruptedException {
        System.out.println(">> 这是a任务:当前时间:" + new Date());
        Thread.sleep(10000);
    }

    @Scheduled(fixedDelay = 5000)
    public void timerTaskB() {
        System.out.println("<< 这是b任务:当前毫秒:" + System.currentTimeMillis());
    }
}

运行一下,Mars酱得到结果如下:

可以看到a任务的输出延迟了15秒,b任务是毫秒数,拿后一个毫秒数减去前一个毫秒数,中间相差也几乎是15秒,看来是被阻塞了啊

怎么解决 @Scheduled 的阻塞?

既然依赖方式是ScheduledExecutorService被ScheduledTaskRegistrar包含,ScheduledTaskRegistrar又是在Spring的后置处理器中使用的,那么我们无法修改Spring的注解后置处理器,只能修改ScheduledTaskRegistrar了,在Spring代码中找到设置这个的部分,代码如下:

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) {
            // 配置ScheduledTaskRegistrar对象
			configurer.configureTasks(this.registrar);
		}
	}

    ...
}

在configurer中配置了ScheduledTaskRegistrar对象啊~。SchedulingConfigurer在Spring源代码中查找到是个接口:

@FunctionalInterface
public interface SchedulingConfigurer {

	/**
	 * Callback allowing a {@link org.springframework.scheduling.TaskScheduler
	 * TaskScheduler} and specific {@link org.springframework.scheduling.config.Task Task}
	 * instances to be registered against the given the {@link ScheduledTaskRegistrar}
	 * @param taskRegistrar the registrar to be configured.
	 */
	void configureTasks(ScheduledTaskRegistrar taskRegistrar);

}

那么我们只要实现这个接口,改变ScheduledTaskRegistrar中ScheduledExecutorService线程池的大小不就可以了?改一下吧:

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10));
    }
}

修改线程是个一个固定大小的线程池,大小为10,再拆分原来的两个定时任务为单独的对象:

/**
 * (这个类的说明)
 *
 * @author mars酱
 */

@Service
public class TimerTaskA {
    @Scheduled(fixedDelay = 5000)
    public void scheduler() throws InterruptedException {
        System.out.println(">> 这是a任务:当前时间:" + new Date());
        Thread.sleep(10000);
    }
}

上面是任务A,下面是任务B,一上一下其乐融融:

/**
 * (这个类的说明)
 *
 * @author mars酱
 */

@Service
public class TimerTaskB {
    @Scheduled(fixedDelay = 2000)
    public void scheduler() {
        System.out.println("<< 这是b任务:当前时间:" + new Date());
    }
}

再修改启动函数:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * @author mars酱
 */

@EnableScheduling
@SpringBootApplication(scanBasePackages = {"com.mars.time"})
public class MarsSpringScheduled {
    public static void main(String[] args) {
        SpringApplication.run(MarsSpringScheduled.class, args);
    }

//    @Async
//    @Scheduled(fixedDelay = 5000)
//    public void timerTaskA() throws InterruptedException {
//        System.out.println(">> 这是a任务:当前时间:" + new Date());
//        Thread.sleep(10000);
//    }
//
    @Async
//    @Scheduled(fixedDelay = 2000)
//    public void timerTaskB() {
//        System.out.println("<< 这是b任务:当前时间:" + new Date());
//    }
}

运行一下,得到结果:

可以看到任务b保证每2秒执行一次,a任务按照自己的频率在执行,各自不影响了。我们设置ScheduledTaskRegistrar中线程池大小是成功的。

为什么要拆?

如果不拆成两个,就算加大Spring定时任务内的线程池大小,也没有用。因为一个对象中包含两个定时任务函数,那个对象在Spring的定时任务框架内是一个对象。

那是不是拆成两个对象,就不会相互影响了呢?也不是,因为默认线程池是单线程,拆成了两个也会阻塞,所以需要加大线程池,而且还要拆成两个对象,这样才解决定时任务的阻塞情况。

可以试试把自定义的ScheduleConfig去掉,然后再启动,得到的结果依然会是阻塞的。

总结

Spring Scheduled注解做定时任务已经支持得很完美了,满足大部分单体架构的定时任务需要。到站下车,下一站见了~

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

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

相关文章

redi可持久化配置

回顾&#xff1a;rpcbind111 nfs2049 redis高可用高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%&#xff0c;99.99%&#xff0c;99.999%等&#xff09; 还要考虑提供主从分离&#xff0c;快速容灾技术&#…

用netty实现聊天

1.引入netty依赖 <dependencies> <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.92.Final</version> </dependency> </dependencies> 2.准备一个服务端ChatServer和…

Redis优化与常见问题

---------------------- Redis 命令工具 ---------------------------------------- redis-server&#xff1a;用于启动 Redis 的工具 redis-benchmark&#xff1a;用于检测 Redis 在本机的运行效率 redis-check-aof&#xff1a;修复 AOF 持久化文件 redis-check-rdb&#xff1…

C语言库函数详解(两万字)

目录 目录 目录 前言 1.C 标准库 - <stdio.h> 简介 1.1 printf&#xff08;&#xff09;&#xff1b; 1.2 scanf(); 1.3 getchar(); 1.4 putchar(); 1.5 gets(); 1.6 puts(); 1.7fscanf(); 1.8 fprintf(); 1.9 fgetc(); 1.10 fgets(); 1.11 fputs(); 1.12 fclose();…

人机融合智能中的诱导引导交互

智能的核心不在于人工智能系统的记忆、注意力和上下文感知等能力&#xff0c;而在于其能够洞察、理解和解决问题的能力。也就是说智能系统需要具备更高层次的思维能力&#xff0c;能够从信息中抽象出本质&#xff0c;识别问题的核心&#xff0c;并提供有效的解决方案。这对于人…

MATLAB绘制动画(四)AVI

MATLAB绘制动画&#xff08;四&#xff09;AVI与GIF clc; clear; close all; fig figure ; aviobj avifile(example.avi); n 100; t 0:2*pi/n:2*pi; x cos(t); y sin(t); for k 1:nx(k) cos(t(k));y(k) sin(t(k));H plot(x,y,x(k),y(k),or,x(k),-y(k),ob);axis equa…

应届生怎么才能提高职场竞争力

前言 随着社会的不断发展&#xff0c;竞争也在逐渐增加。对于应届生来说&#xff0c;提高职场竞争力是很重要的&#xff0c;这样才能更好地融入工作环境、获得更好的机会和待遇。那么&#xff0c;应届生怎么才能提高职场竞争力呢&#xff1f;本文就来简单聊一聊。 应届生步入职…

【再获殊荣】中创算力入选“河南省2023年第五批拟入库科技型中小企业名单”

科技促发展&#xff0c;创新赢未来&#xff01; 科技型中小企业作为最具活力、最具潜力、最具成长性的创新群体&#xff0c;已成为我国在经济转型阶段培育发展新动能的重要载体&#xff01; 2022年&#xff0c;中创算力正式入库河南省2022年科技型中小企业。 2023年&#xf…

F. Array Stabilization (GCD version)

题目链接&#xff1a;传送门 思路&#xff1a; 进行预处理的算法模板&#xff1a; for(int i1;(1<<i)<n;i)for(int j1;j(1<<i)-1<n;j)st[j][i]gcd(st[j][i-1],st[j(1<<(i-1))][i-1]);进行查询的算法模板 int check(int i,int mid){int klog2(mid-i1);…

【Mybatis】Mybatis的动态SQL、缓存机制-三

唠嗑部分 之前我们说了Mybatis的基本操作、关系映射等等&#xff0c;相关文章&#xff1a; 【Mybatis】简单入门及工具类封装-一 【Mybatis】如何实现ORM映射-二 本篇文章和大家说的是Mybatis的动态SQL、缓存机制 言归正传 一、动态SQL MyBatis的映射文件中支持在基础SQL上…

系统分析师:六、企业信息化战略与实施

目录 一、信息与信息化概念 1.1 信息的概念 1.2 信息化的概念 二、信息系统 2.1 信息系统的概念 2.2 信息系统的类型 2.3 信息系统的生命周期 2.4 系统建模 2.5 信息系统战略规划 2.6 信息化开发方法 三、电子政务 四、企业资源计划(ERP) 五、系统集成 一、信息与信息…

k8s1.20版本部署Redis集群(三主三从)——2023.05

文章目录 一、准备工作二、逻辑图三、部署Redis集群1. 安装NFS服务2. 修改API配置3. 创建持久卷PVC4. 创建Redis服务4. 组建Redis集群5. 验证集群 一、准备工作 主机规划 节点IPk8s-master1192.168.2.245k8s-master2192.168.2.246k8s-master3192.168.2.247k8s-node1192.168.2…

【MySQL】-- 库的操作

目录 库的操作 显示数据库 创建数据库 创建数据库案例 删除数据库 删除数据库案例 字符集和校验规则 字符集 校验规则 小结 查看数据库支持的字符集 查看数据库支持的校验规则 校验规则对数据库的影响 不区分大小写 区分大小写 进行查询 结果排序 修改数据库…

我爱我家联合第四范式发布房产经纪大模型

5月15日&#xff0c;在我爱我家年度品牌战略发布会上&#xff0c;我爱我家联合第四范式发布“房产经纪大模型”&#xff0c;将大模型能力用于提升行业生产力的原点&#xff1a;赋能经纪人。本次发布的房产经纪大模型1.0版本已经具备了合格经纪人的基本能力&#xff0c;‍‍‍‍…

leetcode 不同路径详解

文章目录 62 . 不同路径题目详情动态规划之带备忘录实现Java完整代码 63. 不同路径 II题目详情动态规划之带备忘录实现Java完整代码 62 . 不同路径 题目详情 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只…

JavaSE进阶(二)—— 面向对象进阶(包、权限修饰符、抽象类、多态)

目录 一、包 1. 什么是包 2. 导包 二、权限修饰符 1. 什么是权限修饰符 2. 权限修饰符的分类和具体作用范围 3. 学完权限修饰符需要具备如下能力 三、final 1. final的作用 2. final修饰变量的注意 四、常量 1. 常量概述和基本作用 1.1 常量做信息配置的原理、优…

【操作系统】I/O管理

文章目录 I/O设备的基本概念和分类I/O设备的分类 I/O控制器I/O设备的组成I/O控制器的组成I/O控制器的两种寄存器编址方式 I/O控制方式程序直接控制方式中断驱动方式DMA方式通道控制方式 I/O软件的层次结构用户层软件设备独立性软件六大功能逻辑设备表—LUT 设备驱动程序中断处理…

免交互的使用

免交互的使用 一、免交互二、小实验三、利用脚本完成的小实验三、Expect 一、免交互 Here Document 免交互 使用I/O重定向的方式将命令列表提供给交互式程序或命令&#xff0c;比如ftp、cat或者read命令。 免交互时标准输入的一种替代品可以帮助脚本开发人员不必使用临时文件来…

MySQL查询练习题

一&#xff0e;编程题 有三张表&#xff0c;结构如下&#xff1a; Student(学号StuId,姓名StuName,性别StuSex,出生年月StuBirth) Course(课程号CouId,课程名CouName,授课教师CoouTeacher) SC(学号StuId,课程号CouId,成绩Score,授课教师CouTeacher) 用标准SQL语句完成下列操作…

第六章 介绍Productions - 其他Production 选项

文章目录 第六章 介绍Productions - 其他Production 选项用户门户工作流业务活动监控Alerts发布和订阅消息传递Message Bank 第六章 介绍Productions - 其他Production 选项 本章概述了与制作相关的其他选项。 用户门户 与 Studio 和管理门户等后端工具不同&#xff0c;用户…