定时任务实现

news/2024/10/11 20:20:44/

1、定时任务概述

定时任务是一种自动化执行特定操作的方式,可以根据预定的时间、日期或间隔周期性地执行某些任务。

定时任务的作用?

自动化任务执行:定时任务能够在预定的时间触发执行某些任务,无需人工干预。这对于需要定期执行的重复性任务非常有效,例如数据备份、统计报表生成、系统维护等。
提高效率和准确性:通过定时任务,可以在特定的时间段内自动执行任务,避免了人工操作的疏忽和错误。这样可以提高任务的执行效率和准确性,并降低因人为原因导致的错误风险。
节省时间和资源:定时任务可以代替人工手动执行的操作,节省了大量人力资源和时间成本。同时,它也可以合理分配系统资源,避免任务集中导致的系统负载过高。
异步执行:定时任务可以在后台异步执行,不会阻塞用户的其他操作。这对于需要执行耗时较长的任务或需要长时间运行的操作非常有用,可以提高系统的响应速度和用户体验。

2、定时任务的常见几种方式

1)线程类实现定时任务:比如Thread、Runnable、Callable等线程类都可以实现定时任务

2)Timer/TimerTask:Java提供了java.util.Timer和java.util.TimerTask类,可以用于创建定时任务。通过创建一个Timer对象,并调用其schedule()方法,可以指定任务的执行时间和执行间隔。然后,创建一个继承自TimerTask的子类,实现具体的任务逻辑,并在run()方法中定义需要执行的代码。最后,将该任务对象通过Timer的schedule()方法进行调度即可。

3)ScheduledExecutorService:Java提供了java.util.concurrent.ScheduledExecutorService接口,是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。可以用于创建定时任务。通过调用ScheduledExecutorService的scheduleAtFixedRate()或scheduleWithFixedDelay()方法,可以指定任务的执行时间和执行间隔。然后,创建一个实现了Runnable接口的类,实现具体的任务逻辑,并在run()方法中定义需要执行的代码。最后,将该任务对象提交给ScheduledExecutorService进行调度即可。

4)@Scheduled注解:这个是Spring框架所提供的,通过在方法上添加@Scheduled注解,并设置相应的时间表达式,就可以让方法按照指定的时间间隔自动执行。

3、timer实现定时任务

3.1)timer实现定时任务案例

        // 创建一个Timer对象  Timer timer = new Timer();创建一个TimerTask对象,并重写其run方法  TimerTask timerTask = new TimerTask() {int count = 0; @Overridepublic void run() {//执行定时任务的逻辑count++;  System.out.println("定时任务执行次数: " + count);  // 当count等于5时,打印出定时任务执行完毕,并调用timer.cancel()取消定时任务  if (count == 5) {  System.out.println("定时任务执行完毕");  timer.cancel();  }  }};// 延迟1秒后开始执行定时任务,每隔2秒执行一次  timer.schedule(task, 1000, 2000);  

以上案例代表延迟1秒后执行输出次数,每2秒执行1次,输出如下:

定时任务执行次数: 1

定时任务执行次数: 2

定时任务执行次数: 3

定时任务执行次数: 4

定时任务执行次数: 5

定时任务执行完毕

3.2)Timer的常用方法

schedule(TimerTask task, Date time):在指定的时间执行任务。参数task是要执行的任务,参数time是任务的执行时间。
schedule(TimerTask task, long delay):在指定的延迟时间后执行任务。参数task是要执行的任务,参数delay是任务的延迟时间(单位为毫秒)。
schedule(TimerTask task, long delay, long period):在指定的延迟时间后开始执行任务,并按照指定的周期重复执行。参数task是要执行的任务,参数delay是任务的延迟时间(单位为毫秒),参数period是任务的执行周期(单位为毫秒)。
scheduleAtFixedRate(TimerTask task, long delay, long period):在指定的延迟时间后开始执行任务,并以固定的速率重复执行。参数task是要执行的任务,参数delay是任务的延迟时间(单位为毫秒),参数period是任务的执行周期(单位为毫秒)。该方法会尽量保持每次任务执行的时间间隔固定。
cancel():取消所有已安排的任务。调用该方法后,Timer将不再接受新任务,并尝试终止当前正在执行的任务。

3.3)timer的优缺点

优点:JDK自带的,简单易用
缺点
对系统时间敏感:Timer类的任务调度是基于绝对时间的,而不是相对时间。这意味着对系统时间的改变非常敏感,当系统时间发生变化时,可能导致任务执行时间的误差。
单线程执行:Timer类内部使用单个线程来执行所有的定时任务。如果某个任务执行时间过长,会影响其他任务的执行,可能导致任务被延迟。
错误处理能力有限:Timer类的错误处理能力较弱。如果定时任务出现异常并抛出未捕获的异常,Timer类将会停止所有任务的执行。
任务的无法持久化:当应用程序关闭或重启时,Timer 中已经调度的任务会丢失
不适合高并发场景:由于Timer类使用单个线程执行所有任务,不适合在高并发环境下使用。当任务过多或任务执行时间较长时,会影响整体性能和响应性。

3.4)timer的异常捕获

Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,它会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。

例如:

        Timer timer = new Timer();TimerTask timerTask = new TimerTask() {@Overridepublic void run() {int x = 1/0;System.out.println("执行定时任务");}};timer.scheduleAtFixedRate(timerTask, 5 * 1000, 10 * 1000);

执行结果如下:

 

因此如果要防止使用timer因未捕获的异常而使得任务停止,可以在重写run方法内进行异常捕获,修改代码如下:

        Timer timer = new Timer();TimerTask timerTask = new TimerTask() {@Overridepublic void run() {try{System.out.println("定时任务开始");int x = 1/0;System.out.println("执行定时任务");}catch(Exception e){System.out.println("异常捕获");}}};

 运行结果如下:

3.5)timer任务传入参数实现

public class task extends TimerTask{private int x;public task(int x) {this.x = x;}@Overridepublic void run() {//执行定时任务的逻辑System.out.println(x);}public static void main(String[] args) {Timer timer = new Timer();timer.scheduleAtFixedRate(new task(3), 5 * 1000, 10 * 1000);}}

3.6)Timer和TimerTask之间的联系和区别

1) 定义与功能

  1. TimerTask类

    • TimerTask是一个抽象类,它实现了Runnable接口的run()方法。
    • 主要功能:定义要执行的具体任务逻辑。它本身不执行任何任务,而是等待Timer来调度执行。
  2. Timer类

    • Timer是一个工具类,用于调度一个线程以在将来某个时间执行指定的任务。
    • 主要功能:作为定时器,负责调度和管理TimerTask任务。它可以安排任务执行一次,或者定期重复执行。

2) 协作方式

  • Timer和TimerTask通常成对出现。Timer作为定时器,负责调度和管理TimerTask任务;而TimerTask则是定时任务的具体实现,由Timer来调度执行。
  • 一个Timer可以调度任意多个TimerTask,所有任务都存储在一个队列中顺序执行。如果需要多个TimerTask并发执行,则需要创建多个Timer实例(因为每个Timer仅对应一个线程)。

3) 使用场景与特点

  1. TimerTask

    • 适用于需要在指定时间执行任务的场景。
    • 提供了简单的接口,使得任务的调度变得简单。
    • 可以根据需要设置任务的执行时间和执行周期。
  2. Timer

    • 适用于需要调度和管理多个定时任务的场景。
    • 线程安全,但不保证任务执行的精确性(因为基于单线程执行)。
    • 如果某个任务很耗时,可能会影响其他计划任务的执行。因此,在JDK 1.5及以上版本中,建议使用ScheduledThreadPoolExecutor来代替Timer执行计划任务。

4) 异常处理与线程管理

  1. 异常处理

    Timer线程不会捕获异常。如果TimerTask抛出了未检查的异常,会终止Timer线程,从而导致其他计划任务无法得到继续执行。因此,在TimerTask的run()方法中捕获所有可能的异常是非常重要的。
  2. 线程管理

    • Timer内部使用了一个线程(TimerThread)来顺序执行所有的TimerTask任务。
    • 可以通过调用Timer.cancel()方法来终止Timer线程。另外,如果创建Timer时将其设置为守护线程(使用new Timer(true)),则当且仅当进程结束时,该守护线程会自动注销。

4、ScheduledExecutorService实现定时任务

ScheduledExecutorService 是 Java 中用于调度任务的接口,它继承自 ExecutorService,提供了在给定延迟后运行任务、定期执行任务等能力

4.1)ScheduledExecutorService实现定时任务案例

// 创建一个ScheduledThreadPoolExecutor,这里使用1个线程
//        ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);// 创建一个实现了Runnable接口的任务Runnable task = new Runnable() {@Overridepublic void run() {//执行定时逻辑System.out.println();}};// 安排任务在初始延迟后执行,然后每隔指定的周期重复执行(这里使用不重复执行作为示例)// 如果需要重复执行,请使用scheduleAtFixedRate或scheduleWithFixedDelay方法
//        executor.schedule(task, 0, TimeUnit.SECONDS); // 立即执行,延迟时间为0秒executor.scheduleWithFixedDelay(task, 5, 10, TimeUnit.SECONDS);// 注意:在实际应用中,您可能需要在某个时刻关闭executor,以避免资源泄露// 这通常是在应用程序关闭或不再需要定时任务时进行的// 例如,您可以在main方法的最后添加以下代码(但这将阻止程序立即退出):// executor.shutdown();// 如果希望等待所有任务完成后再关闭,可以使用:// executor.shutdownNow(); // 或者更优雅地等待任务完成:executor.awaitTermination(...);

4.2)ScheduledExecutorService的常用方法

schedule(Runnable command, long delay, TimeUnit unit): 在指定的延迟时间后执行一次任务。
schedule(Callable<V> callable, long delay, TimeUnit unit): 在指定的延迟时间后执行一次任务,并返回一个可获取结果的 Future 对象。
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit): 在初始延迟时间后开始执行任务,并以固定的时间间隔重复执行任务。
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit): 在初始延迟时间后开始执行任务,并在每次任务完成后延迟指定的时间再执行下一次任务。
submit(Callable<V> task): 提交一个可获取结果的任务,并返回一个表示任务执行结果的 Future 对象。
submit(Runnable task): 提交一个不返回结果的任务,并返回一个表示任务执行完成的 Future 对象。
shutdown(): 优雅地关闭 ScheduledExecutorService,等待已提交的任务执行完毕。
shutdownNow(): 强制关闭 ScheduledExecutorService,立即停止所有任务的执行。

注意:

虽然使用ScheduledExecutorService实现定时任务如果未捕获异常线程不会像用timer实现定时任务而终止,但是如果遇到了未捕获的异常,此定时任务会停止执行,不会开始下一次定时任务,线程会阻塞在异常处,因此即使使用ScheduledExecutorService实现定时任务也建议在run方法内捕获异常,保证即使这次遇到异常,下次任务能够定时执行。

4.3)Timer VS ScheduledExecutorService

Timer 和 ScheduledExecutorService 都是 Java 中用于执行定时任务的工具,但它们之间存在一些关键差异。以下是对这两者的详细比较:

1)线程模型

  1. Timer
    • Timer 是单线程的,每创建一个 Timer 实例,就会创建一个新的线程(TimerThread)来执行任务。
    • 如果一个 TimerTask 执行的时间过长,它会独占 Timer 对象,导致后续的任务无法及时执行,必须等待当前任务完成后才能继续。
    • Timer 默认情况下不是守护线程,但可以设置为守护线程(new Timer(true))。当进程中没有其他非守护线程时,守护线程将销毁。
  2. ScheduledExecutorService
    • ScheduledExecutorService 是基于线程池实现的,支持多个任务并发执行。
    • 线程池中的线程数量可以根据需求进行配置,因此可以同时执行多个任务,而不会相互阻塞。
    • ScheduledExecutorService 提供了更灵活和强大的线程管理功能。

2)任务调度

  1. Timer
    • Timer 对调度的支持是基于绝对时间的,因此任务对系统时钟的改变是敏感的。
    • 如果 TimerTask 抛出未检查的异常,Timer 将会产生无法预料的行为。具体来说,Timer 线程并不捕获异常,所以 TimerTask 抛出的未检查的异常会终止 Timer 线程。此时,已经被安排但尚未执行的 TimerTask 永远不会再执行了,新的任务也不能被调度。
  2. ScheduledExecutorService
    • ScheduledExecutorService 只支持相对时间进行调度。
    • 它提供了 scheduleAtFixedRate 和 scheduleWithFixedDelay 两种方法,允许更灵活地安排任务的执行。
    • 如果任务抛出异常,它不会影响其他任务的执行(除非异常导致整个线程池被关闭)。

3)任务取消与资源管理

  1. Timer
    • Timer 中的 cancel() 方法可以将任务队列中的全部任务进行取消,但有时并不一定停止任务,因为 Timer 类中的 cancel() 方法有时并没有竞争到锁。
    • 由于 Timer 是单线程的,取消任务时可能会遇到一些竞争条件,导致任务取消不完全或延迟。
  2. ScheduledExecutorService
    • 通过 ScheduledExecutorService 安排的任务可以通过 Future 对象进行取消。调用 Future 对象的 cancel 方法可以请求取消执行此任务。
    • ScheduledExecutorService 提供了更细粒度的任务管理功能,可以单独取消某个任务,而不会影响其他任务的执行。
    • 当不再需要使用 ScheduledExecutorService 时,应该及时关闭它以释放系统资源。可以使用 shutdown 或 shutdownNow 方法来关闭线程池。

4)使用建议

  • 如果需要执行简单的、少量的定时任务,并且不需要并发执行,那么 Timer 可能是一个合适的选择。
  • 如果需要执行大量的定时任务,或者需要并发执行多个任务,那么 ScheduledExecutorService 是更好的选择。它提供了更强大的线程管理功能和更灵活的任务调度能力。

5、@Scheduled注解实现定时任务

在Spring框架中,@Scheduled注解提供了一种非常方便的方式来实现定时任务。

5.1)@Scheduled注解定时任务实现案例

首先,确保你的Spring项目已经包含了必要的依赖项,比如spring-contextspring-scheduling。如果你使用的是Maven项目,可以在pom.xml中添加以下依赖(如果Spring Boot已经包含了这些依赖,则无需重复添加):

<dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-context</artifactId>  <version>你的Spring版本</version>  
</dependency>  
<dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-scheduling</artifactId>  <version>你的Spring版本</version>  
</dependency>

然后,在你的Spring配置类(通常是带有@Configuration注解的类)或Spring Boot的主类上添加@EnableScheduling注解,以启用Spring的计划任务功能:

import org.springframework.context.annotation.Configuration;  
import org.springframework.scheduling.annotation.EnableScheduling;  @Configuration  
@EnableScheduling  
public class SchedulingConfig {  // 这里可以添加其他配置  
}

 接下来,创建一个包含定时任务的类,并使用@Scheduled注解来标记定时方法。例如:

import org.springframework.scheduling.annotation.Scheduled;  
import org.springframework.stereotype.Component;  @Component  
public class ScheduledTasks {  // 每5秒执行一次  @Scheduled(fixedRate = 5000)  public void reportCurrentTime() {  System.out.println("当前时间: " + System.currentTimeMillis());  }  // 每天凌晨1点执行一次(需要cron表达式)  @Scheduled(cron = "0 0 1 * * ?")  public void executeDailyTask() {  System.out.println("执行每日任务: " + System.currentTimeMillis());  }  
}

5.2)@Scheduled注解属性介绍

cron():用于指定Cron表达式,表示任务的执行时间规则。例如0 0 * * * ?表示每天的凌晨12点执行一次任务。
zone():用于指定Cron表达式的时区,默认为空字符串。如果需要根据不同的时区执行任务,则可以设置该属性。
fixedDelay()和fixedDelayString():用于指定任务的固定延迟时间,即任务结束后等待多长时间再次执行。默认值为-1,表示不使用固定延迟。
fixedRate()和fixedRateString():用于指定任务的固定频率,即任务开始执行后多长时间再次执行。默认值为-1,表示不使用固定频率。
initialDelay()和initialDelayString():用于指定任务的初始延迟时间,即任务首次执行前等待多长时间。默认值为-1,表示立即执行。
timeUnit():用于指定时间单位,可选值有TimeUnit.MILLISECONDS(毫秒,默认值)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分钟)等

fixedDelay + initialDelay 属性功能上就等价于 Timer 的schedule方法了,

fixedRate+initialDelay属性功能上就等价于 Timer 的 scheduleAtFixedRate 方法了

6、分布式定时任务

前面所有的定时任务,无论是基于线程类,还是基于 JDK 自带的定时任务,还是基于Spring提供的Spring Task,都无法在分布式环境下使用,并且不支持持久化,一旦服务重启所有的定时任务都将发生丢失,所以我们需要使用到其它的第三方成熟的定时任务框架。

Quartz:是一个功能强大的开源作业调度框架,用于在Java应用程序中实现定时任务调度和作业调度。

XXL-Job:是一个轻量级分布式任务调度平台。特点是平台化,易部署,开发迅速、学习简单、轻量级、易扩展。由调度中心和执行器功能完成定时任务的执行。调度中心负责统一调度,执行器负责接收调度并执行。

Elastic-Job:是一个开源的分布式任务调度解决方案,它是基于Java的轻量级分布式调度框架

三者的比较

功能和特性: Quartz:Quartz是一个功能强大的作业调度框架,支持灵活的任务调度策略、分布式集群、任务持久化等特性。它具有丰富的API和扩展点,可以根据需求进行定制开发和扩展。 XXL-Job:XXL-Job是一个分布式任务调度平台,提供了可视化操作界面、多种任务调度方式、分片任务支持等特性。它注重于任务的管理和监控,并提供了报警与告警功能。 Elastic-Job:Elastic-Job是一个轻量级的分布式任务调度解决方案,支持分布式任务调度、弹性扩缩容、任务监控和管理等特性。它注重于任务的弹性扩展和容错机制。

分布式支持: Quartz:Quartz在分布式场景中需要基于数据库锁来保证操作的唯一性,通过多个节点的异步运行实现高可用性。但它没有执行层面的任务分片机制。 XXL-Job:XXL-Job提供了分布式集群的支持,可以实现任务的负载均衡和高可用性。它支持分片任务和动态调整任务节点数量的特性。 Elastic-Job:Elastic-Job支持分布式任务调度,具备弹性扩缩容能力,可以根据任务的执行情况动态调整任务节点数量。

可视化和管理界面: Quartz:Quartz本身没有提供可视化的任务管理界面,需要通过其他工具或自行开发来实现。 XXL-Job:XXL-Job提供了简洁直观的任务管理界面,方便用户进行任务的创建、编辑、状态查看等操作。 Elastic-Job:Elastic-Job提供了任务监控和管理功能,可以查看任务的执行日志、运行状态、统计信息等。

社区活跃度和生态系统: Quartz:Quartz是一个非常成熟且广泛使用的作业调度框架,拥有强大的社区支持和丰富的生态系统。 XXL-Job:XXL-Job也有一个活跃的社区,并且在国内得到广泛应用和认可。 Elastic-Job:Elastic-Job相对较新,并且社区规模较小,但其在分布式任务调度领域有一定的影响力。

Quartz在功能和扩展性上非常强大,适用于复杂的任务调度需求。XXL-Job注重于任务管理和监控,并提供了可视化的操作界面。Elastic-Job轻量级且具备分布式任务调度和弹性扩缩容能力。

总结

线程+休眠实现定时任务,是最简单实现定时任务的方式了,但这只是提供一种思路,实际开发中几乎不会使用

JDK自带的定时任务Timer和ScheduledExecutorService,我们需要了解两者的区别

Timer是单线程的,一旦发生异常,将终止所有的任务;Timer是绝对时间的,会受到系统时间的影响 ScheduledExecutorService是基于线程池,是多线程的,一旦发生异常,不会终止所有的任务;ScheduledExecutorService是相对时间 ,不会受到系统时间的影响 注意区固定间隔和固定频率的区别 Spring Task实现的定时任务是基于线程池,是多线程的,一旦发生异常,不会终止所有的任务;基于相对时间,不会受到系统时间的影响

分布式定时任务,一般是直接使用第三方成熟的定时任务框架,当然如果你公司资金充足可以选择开发定制化定时任务框架。选用开源的第三方成熟定时任务框架,好处在于功能完善、免费,代码质量也是有保障的


http://www.ppmy.cn/news/1537614.html

相关文章

从粉尘爆炸事故,看火灾中为什么要加强通风

2024年1月20日&#xff0c;江苏常州一工厂发生粉尘爆炸事故&#xff0c;造成了8死8伤的惨重后果。 粉尘爆炸&#xff0c;指的是空气中漂浮的可燃性粉尘&#xff0c;累积到一定的浓度&#xff0c;遇到明火、静电或者高温时&#xff0c;被瞬间点燃&#xff0c;进而在有限空间内迅…

【书生浦语实战】MindSearch 部署到HuggingFace Space

结果速览 欢迎来玩&#xff1a;https://huggingface.co/spaces/LLyn/mindsearch_exercise 配置开发环境 使用github codespace 第一次使用github的codespace&#xff5e;本质上跟在intern studio一样&#xff0c;但是页面是vscode效果&#xff08;intern studio是linux cl…

【PostgreSQL】PG数据库表“膨胀”粗浅学习

文章目录 1 为什么需要关注表膨胀&#xff1f;2 如何确定是否发生了表膨胀&#xff1f;2.1 通过查询表的死亡元组占比情况来判断膨胀率2.1.1 指定数据库和表名2.1.2 查询数据库里面所有表的膨胀情况 3 膨胀的原理3.1 什么是膨胀&#xff1f;膨胀率&#xff1f;3.2 哪些数据库元…

Spring Cache 的说明及常用注解

一.介绍 Spring Cache是Spring Framework中的一个模块&#xff0c;用于简化和统一缓存的使用。它提供了一种将缓存逻辑与应用程序业务逻辑分离的方式&#xff0c;使得我们可以更方便地使用缓存来提高应用程序的性能。 二.主要特性 注解支持&#xff1a;Spring Cache提供了一组…

通过onnxruntime进行模型部署过程中的问题

​ 1. onnxruntime包下载 从https://github.com/microsoft/onnxruntime/releases/下载解压到E:/code/package/onnruntime 2. opencv_c下载https://github.com/opencv/opencv/releases/tag/4.8.1 3.测试opencv代码&#xff1a;总结&#xff1a;添加include目录&#xff0c;添…

Leetcode 买卖股票的最佳时机

这段代码的目的是解决“买卖股票的最佳时机”这个问题&#xff0c;即在给定的股票价格数组中&#xff0c;找到一次买入和卖出所能获得的最大利润。 算法思想&#xff1a; 定义两个变量&#xff1a; minPrice: 这个变量用于记录迄今为止遇到的最小股票价格&#xff08;买入价格…

Stable Diffusion绘画 | 签名、字体、Logo设计

第1步&#xff0c;使用 PS&#xff08;小白推荐使用 可画&#xff09;准备一个 512*768 的签名、字体、Logo图片&#xff1a; 第2步&#xff0c;来到模型网站&#xff0c;搜索&#x1f50d;关键词“电商”&#xff0c;找到一款喜欢的 LoRA&#xff1a; 第3步&#xff0c;选择一…

学习JavaScript的真假值然后理解!!运算符的使用

真值&#xff08;truthy&#xff09;和假值&#xff08;falsy&#xff09; 在 JavaScript 中&#xff0c;有一些值总是被认为是假值&#xff08;falsy&#xff09;&#xff0c;其他的都被认为是真值&#xff08;truthy&#xff09;。以下是常见的假值&#xff1a; false0&…