当前位置:

首页 > 编程开发 > Java Spring怎么实现定时任务

Java Spring怎么实现定时任务

java实现定时任务Jdk自带的库中,有两种方式可以实现定时任务,一种是Timer,另一种是ScheduledThreadPoolExecutor。Timer+TimerTask创建一个Timer就创建了一个线程,可以用来调度TimerTask任务Timer有四个构造方法,可以指定Timer线程的名字以及是否设置为为守护线程。默认名字Timer-编号,默认不是守护线程。主要有三个比较重要的方法:cancel():终止任务调度,取消当前调度的所有任务,正在运行的任务不受影响purge():从任务队列中移除所

本文目录

    java实现定时任务

    Jdk自带的库中,有两种方式可以实现定时任务,一种是Timer,另一种是ScheduledThreadPoolExecutor。

    Timer+TimerTask

    创建一个Timer就创建了一个线程,可以用来调度TimerTask任务

    Java Spring怎么实现定时任务

    Timer有四个构造方法,可以指定Timer线程的名字以及是否设置为为守护线程。默认名字Timer-编号,默认不是守护线程。

    主要有三个比较重要的方法:

    cancel():终止任务调度,取消当前调度的所有任务,正在运行的任务不受影响

    purge():从任务队列中移除所有已经取消的任务

    schedule:开始调度任务,提供了几个重载方法:

    schedule(TimerTask task, long delay)延时执行,表示delay毫秒后执行一次task任务

    schedule(TimerTask task, Date time)`指定时间执行,到`time`时间的时候执行一次`task
    schedule(TimerTask task, long delay, long period)`延时周期执行,经过`delay`毫秒后每`period`毫秒执行一次`task
    schedule(TimerTask task, Date firstTime, long period)`指定时间后周期执行,到达指定时间`firstTime`后每`period`毫秒执行一次`task

    示例

    public class TimerTest {
        public static void main(String[] args) {
            Timer timer = new Timer("aa");
            Task task = new Task();
            timer.schedule(task,new Date(),1000);
        }
    }
    class Task extends TimerTask{
        @Override
        public void run() {
            System.out.println(new Date());
        }
    }

    输出:
    Thu Jul 07 14:50:02 CST 2022
    Thu Jul 07 14:50:03 CST 2022
    Thu Jul 07 14:50:04 CST 2022
    Thu Jul 07 14:50:05 CST 2022
    …………

    弊端

    Timer是单线程的,并且不会抛出异常,一旦定时任务抛出异常,将会导致整个线程停止,即定时任务停止。

    ScheduledThreadPoolExecutor

    因为Timer的缺陷,所以不建议使用Timer,建议使用ScheduledThreadPoolExecutor。

    ScheduledThreadPoolExecutor是Timer的替代者,于JDK1.5引入,继承了ThreadPoolExecutor,是基于线程池设计的定时任务类。

    主要的调度方法:

    Java Spring怎么实现定时任务

    schedule只执行一次调度,(任务,延迟时间,延迟时间单位)

    scheduleAtFixedRate按固定的频率调度,如果执行时间过长,下次调度会延迟,(任务,第一次执行的延迟时间,周期,时间单位)

    scheduleWithFixedDelay延迟调度,一次任务执行完后加上延迟时间执行下一次任务,(任务,第一次执行的延迟时间,间隔时间,时间单位)

    示例

    public class TimerTest {
        public static void main(String[] args) throws Exception{
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
            scheduledExecutorService.scheduleAtFixedRate(
                    () -> System.out.println(new Date()),
                    1,3, TimeUnit.SECONDS);
        }
    }

    Spring定时任务

    Spring定时任务主要靠@Scheduled注解实现,corn,fixedDelay,fixedDelayString,fixedRate,fixedRateString五个参数必须指定其一,传两个或三个都会抛出异常

    @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Repeatable(Schedules.class)
    public @interface Scheduled {
    	String CRON_DISABLED = ScheduledTaskRegistrar.CRON_DISABLED;
        // cron表达式
    	String cron() default "";
        // 时区
    	String zone() default "";
        // 从上一次调用结束到下一次调用之间的固定时间
    	long fixedDelay() default -1;
        // 和fixedDelay意思相同,只是使用字符传格式,支持占位符。例如:fixedDelayString = "${time.fixedDelay}"
    	String fixedDelayString() default "";
        // 两次调用之间固定的毫秒数(不需要等待上次任务完成)
    	long fixedRate() default -1;
        // 同上,支持占位符
    	String fixedRateString() default "";
        // 第一次执行任务前延迟的毫秒数
    	long initialDelay() default -1;
        // 同上,支持占位符
    	String initialDelayString() default "";
    }

    示例

    @Component
    @EnableScheduling
    public class ScheduledTask {
        @Scheduled(fixedDelay = 1000)
        public void task(){
            System.out.println("aaa");
        }
    }

    原理

    项目启动ScheduledAnnotationBeanPostProcessor的postProcessAfterInitialization()方法扫描带有@Scheduled注解的方法:

    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) {
    		if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
    				bean instanceof ScheduledExecutorService) {
    			// Ignore AOP infrastructure such as scoped proxies.
    			return bean;
    		}
    		Class targetClass = AopProxyUtils.ultimateTargetClass(bean);
    		if (!this.nonAnnotatedClasses.contains(targetClass) &&
    				AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
    			Map> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
    					(MethodIntrospector.MetadataLookup>) method -> {
    						Set scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
    								method, Scheduled.class, Schedules.class);
    						return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
    					});
    			if (annotatedMethods.isEmpty()) {
    				this.nonAnnotatedClasses.add(targetClass);
    				if (logger.isTraceEnabled()) {
    					logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
    				}
    			}
    			else {
    				// Non-empty set of methods
    				annotatedMethods.forEach((method, scheduledMethods) ->
                            // 调用processScheduled方法将定时任务的方法存放到任务队列中
    						scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
    				if (logger.isTraceEnabled()) {
    					logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
    							"': " + annotatedMethods);
    				}
    			}
    		}
    		return bean;
    	}
    	protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
    		try {
                // 创建任务线程
    			Runnable runnable = createRunnable(bean, method);
                // 解析到定时任务方式的标记,解析到正确的参数后会设置为TRUE,如果在解析到了其他的参数就会抛出异常
    			boolean processedSchedule = false;
    			String errorMessage =
    					"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
    			Set tasks = new LinkedHashSet<>(4);
    			// Determine initial delay 解析第一次的延迟(解析initialDelay参数)
    			long initialDelay = scheduled.initialDelay();
    			String initialDelayString = scheduled.initialDelayString();
    			if (StringUtils.hasText(initialDelayString)) {
                    // initialDelay不能小于0
    				Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
    				if (this.embeddedValueResolver != null) {
    					initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
    				}
    				if (StringUtils.hasLength(initialDelayString)) {
    					try {
    						initialDelay = parseDelayAsLong(initialDelayString);
    					}
    					catch (RuntimeException ex) {
    						throw new IllegalArgumentException(
    								"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
    					}
    				}
    			}
    			// Check cron expression 解析cron表达式
    			String cron = scheduled.cron();
    			if (StringUtils.hasText(cron)) {
                    // 解析时区
    				String zone = scheduled.zone();
    				if (this.embeddedValueResolver != null) {
    					cron = this.embeddedValueResolver.resolveStringValue(cron);
    					zone = this.embeddedValueResolver.resolveStringValue(zone);
    				}
    				if (StringUtils.hasLength(cron)) {
    					Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
    					processedSchedule = true;
    					if (!Scheduled.CRON_DISABLED.equals(cron)) {
    						TimeZone timeZone;
    						if (StringUtils.hasText(zone)) {
    							timeZone = StringUtils.parseTimeZoneString(zone);
    						}
    						else {
    							timeZone = TimeZone.getDefault();
    						}
    						tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
    					}
    				}
    			}
                // 第一次延迟参数小于0,默认为0
    			// At this point we don't need to differentiate between initial delay set or not anymore
    			if (initialDelay < 0) {
    				initialDelay = 0;
    			}
    			// Check fixed delay 解析fixedDelay参数
    			long fixedDelay = scheduled.fixedDelay();
    			if (fixedDelay >= 0) {
    				Assert.isTrue(!processedSchedule, errorMessage);
    				processedSchedule = true;
    				tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
    			}
    			String fixedDelayString = scheduled.fixedDelayString();
    			if (StringUtils.hasText(fixedDelayString)) {
    				if (this.embeddedValueResolver != null) {
    					fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
    				}
    				if (StringUtils.hasLength(fixedDelayString)) {
    					Assert.isTrue(!processedSchedule, errorMessage);
    					processedSchedule = true;
    					try {
    						fixedDelay = parseDelayAsLong(fixedDelayString);
    					}
    					catch (RuntimeException ex) {
    						throw new IllegalArgumentException(
    								"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
    					}
    					tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
    				}
    			}
    			// Check fixed rate 解析fixedRate参数
    			long fixedRate = scheduled.fixedRate();
    			if (fixedRate >= 0) {
    				Assert.isTrue(!processedSchedule, errorMessage);
    				processedSchedule = true;
    				tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
    			}
    			String fixedRateString = scheduled.fixedRateString();
    			if (StringUtils.hasText(fixedRateString)) {
    				if (this.embeddedValueResolver != null) {
    					fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
    				}
    				if (StringUtils.hasLength(fixedRateString)) {
    					Assert.isTrue(!processedSchedule, errorMessage);
    					processedSchedule = true;
    					try {
    						fixedRate = parseDelayAsLong(fixedRateString);
    					}
    					catch (RuntimeException ex) {
    						throw new IllegalArgumentException(
    								"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
    					}
    					tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
    				}
    			}
    			// Check whether we had any attribute set
                // 如果五个参数一个也没解析到,抛出异常
    			Assert.isTrue(processedSchedule, errorMessage);
    			// Finally register the scheduled tasks
                // 并发控制将任务队列存入注册任务列表
    			synchronized (this.scheduledTasks) {
    				Set regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
    				regTasks.addAll(tasks);
    			}
    		}
    		catch (IllegalArgumentException ex) {
    			throw new IllegalStateException(
    					"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
    		}
    	}

    将任务解析并添加到任务队列后,交由ScheduledTaskRegistrar类的scheduleTasks方法添加(注册)定时任务到环境中

    protected void scheduleTasks() {
       if (this.taskScheduler == null) {
           //获取ScheduledExecutorService对象,实际上都是使用ScheduledThreadPoolExecutor执行定时任务调度
          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));
          }
       }
    }
    private void addScheduledTask(@Nullable ScheduledTask task) {
       if (task != null) {
          this.scheduledTasks.add(task);
       }
    }
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。