在Java开发中,定时任务是一种十分常见的功能.

定时任务是在约定时间内执行的一段程序

如每天凌晨24点备份同步数据,又或者电商平台 30 分钟后自动取消未支付的订单,每隔一个小时拉取一次数据等都需要使用到定时器

  • 批量处理数据:批量统计上个月的某个数据。
  • 时间驱动的场景:某个时间点发送短信、邮件。
  • 固定频率的场景:每隔5分钟需要执行一次

在Java中,实现定时任务的方式有很多,最简单的在线程中通过JDK自带TimerThread.sleep睡眠线程,或者采用SpringBoot中的@Schedule注解,或者采用定时线程池ScheduledExecutorService来实现,又或者采用Spring Boot中集成Quartz框架实现

一、Thread线程等待(最原始最简单方式)

创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果

匿名内部类实现java.lang.Runnable 接口

/** * 线程等待;实现java.lang.Runnable接口 */public class ThreadTask {public static void main(String[] args) {final long timeInterval = 1000;//创建线程(匿名内部类方式)Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (true){SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("线程等待实现定时任务:" + dateStr);try {Thread.sleep(timeInterval);} catch (InterruptedException e) {e.printStackTrace();}}}});//开启线程thread.start();}}

自定义类实现 java.lang.Runnable 接口

/** * 线程等待;自定义类实现java.lang.Runnable接口 */public class ThreadTask1 {public static void main(String[] args) {//自定义类实现java.lang.Runnable接口MyRunnable runnable = new MyRunnable();//创建线程(自定义类MyRunnable实现java.lang.Runnable接口)Thread t = new Thread(runnable);//开启线程t.start();}}/** * 自定义类MyRunnable实现java.lang.Runnable接口 */class MyRunnable implements Runnable{final long timeInterval = 1000;@Overridepublic void run() {while (true){SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("线程等待实现定时任务1:" + dateStr);try {Thread.sleep(timeInterval);} catch (InterruptedException e) {e.printStackTrace();}}}}

二、Timer(最古老方式)

JDK自带的Timer API算是最古老的定时任务实现方式了。Timer是一种定时器工具,使用java.util.Timer工具类。用来在一个后台线程计划执行指定任务。它可以安排任务“执行一次”或者定期“执行多次”。

/** * Timer是JAVA自带的定时任务类;优点:使用方便 */public class MyTimerTask {public static void main(String[] args) {// 定义一个定时任务TimerTask timerTask = new TimerTask() {@Overridepublic void run() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("运行定时任务:" + dateStr);}};// 计时器Timer timer = new Timer();// 添加执行任务(延迟 1s 执行,每 3s 执行一次)timer.schedule(timerTask, 1000, 3000);}}

Timer类核心方法如下:

// 在指定延迟时间后执行指定的任务schedule(TimerTask task,long delay);// 在指定时间执行指定的任务。(只执行一次)schedule(TimerTask task, Date time);// 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执行指定的任务schedule(TimerTask task,long delay,long period);// 在指定的时间开始按照指定的间隔(period)重复执行指定的任务schedule(TimerTask task, Date firstTime , long period);// 在指定的时间开始进行重复的固定速率执行任务scheduleAtFixedRate(TimerTask task,Date firstTime,long period);// 在指定的延迟后开始进行重复的固定速率执行任务scheduleAtFixedRate(TimerTask task,long delay,long period);// 终止此计时器,丢弃所有当前已安排的任务。cancal();// 从此计时器的任务队列中移除所有已取消的任务。purge();

Timer 缺点分析

1、任务执行时间长影响其他任务

当一个任务的执行时间过长时,会影响其他任务的调度

/** * Timer是JAVA自带的定时任务类;优点:使用方便 */public class MyTimerTask1 {public static void main(String[] args) {// 定时任务1TimerTask timerTask = new TimerTask() {@Overridepublic void run() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("进入定时任务1:" + dateStr);try {// 休眠 5 秒TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}dateStr = sdf.format(new Date());System.out.println("运行定时任务1:" + dateStr);}};// 定时任务2TimerTask timerTask2 = new TimerTask() {@Overridepublic void run() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("运行定时任务2:" + dateStr);}};// 计时器Timer timer = new Timer();// 添加执行任务(延迟 1s 执行,每 2s 执行一次)timer.schedule(timerTask, 1000, 2000);timer.schedule(timerTask2, 1000, 2000);}}

当任务 1 运行时间超过设定的间隔时间时,任务 2 也会延迟执行原本任务 1 和任务 2 的执行时间间隔都是 2s,但因为任务 1 执行了 5s,因此任务 2 的执行时间间隔也变成了 10s(和原定时间不符)

2、任务异常影响其他任务

使用 Timer 类实现定时任务时,当一个任务抛出异常,其他任务也会终止运行

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

/** * Timer是JAVA自带的定时任务类;优点:使用方便 */public class MyTimerTask2 {public static void main(String[] args) {// 定时任务1TimerTask timerTask = new TimerTask() {@Overridepublic void run() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("进入定时任务1:" + dateStr);//发生异常int num = 10 / 0;dateStr = sdf.format(new Date());System.out.println("运行定时任务1:" + dateStr);}};// 定时任务2TimerTask timerTask2 = new TimerTask() {@Overridepublic void run() {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("运行定时任务2:" + dateStr);}};// 计时器Timer timer = new Timer();// 添加执行任务(延迟 1s 执行,每 2s 执行一次)timer.schedule(timerTask, 1000, 2000);timer.schedule(timerTask2, 1000, 2000);}}

Timer 小结

Timer 类实现定时任务的优点是方便,因为它是 JDK 自定的定时任务,但缺点是任务如果执行时间太长或者是任务执行异常,会影响其他任务调度

三、ScheduledExecutorService;ScheduledThreadPool

ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行(任务是并发执行,互不影响)

ScheduledExecutorService可以实现Timer具备的所有功能,并解决了 Timer类存在的问题

注:

只有当执行调度任务时,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态

/** * ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行(任务是并发执行,互不影响) * ScheduledExecutorService可以实现Timer具备的所有功能,并解决了 Timer类存在的问题 */public class MyScheduledExecutorService {public static void main(String[] args) {// 创建任务队列;10为线程数量ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);//执行任务scheduledExecutorService.scheduleAtFixedRate(() -> {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("ScheduledExecutorService执行定时任务:" + dateStr);},1,2, TimeUnit.SECONDS);// 1s 后开始执行,每 2s 执行一次}}

public class ScheduledThreadPool {public static void main(String[] args) {// 参数代表可以同时执行的定时任务个数ScheduledExecutorService service = Executors.newScheduledThreadPool(3);/** * schedule:延时2秒执行一次任务 */service.schedule(() -> {System.out.println("task0-start");sleep(2);System.out.println("task0-end");}, 2, TimeUnit.SECONDS);/** * scheduleAtFixedRate:1秒后,每间隔2秒执行一次任务 * 注意,如果任务的执行时间(例如6秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务 */service.scheduleAtFixedRate(() -> {System.out.println("task1-start");sleep(2);System.out.println("task1-end");}, 1, 2, TimeUnit.SECONDS);/** * scheduleWithFixedDelay:1秒后,每次延时2秒执行一次任务 * 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务 */service.scheduleWithFixedDelay(() -> {System.out.println("task2-start");sleep(2);System.out.println("task2-end");}, 1, 2, TimeUnit.SECONDS);}private static void sleep(long time) {try {TimeUnit.SECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}}}

ScheduledExecutorService主要有以下4个方法

ScheduledFuturepublic ScheduledFuture scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);

command为被执行的线程;initialDelay为初始化后延时执行时间;period为两次开始执行最小间隔时间;unit为计时单位

scheduleWithFixedDelay方法

public ScheduledFuture scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);

command为被执行的线程;initialDelay为初始化后延时执行时间;period为前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间);unit为计时单位

ScheduledExecutorService;ScheduledThreadPool小结

在单机生产环境下建议使用 ScheduledExecutorService 来执行定时任务,它是 JDK 1.5 之后自带的 API,因此使用起来也比较方便,并且使用 ScheduledExecutorService 来执行任务,不会造成任务间的相互影响

四、Spring Task;@Scheduled

Spring 3开始,Spring自带了一套定时任务工具Spring-Task(基于注解 [@Scheduled,@EnableScheduling] 形式实现,可以把它看成是一个轻量级的Quartz,使用起来十分简单,除Spring相关的包外不需要额外的包,支持注解和配置文件两种形式。通常情况下在Spring体系内,针对简单的定时任务,可直接使用Spring提供的功能。

如果使用的是 Spring 或 Spring Boot 框架,可以直接使用 Spring Framework 自带的定时任务,使用上述定时任务的实现方式,很难实现设定了具体时间的定时任务,如当我们需要每周五来执行某项任务时,但如果使用 Spring Task 就可轻松的实现

以 Spring Boot 为例,实现定时任务只需两步:

开启定时任务

添加定时任务

1、开启定时任务

如果是在Spring Boot项目中,需要在启动类上添加@EnableScheduling来开启定时任务

@EnableScheduling // 开启定时任务@SpringBootApplicationpublic class Job4ScheduledApplication {public static void main(String[] args) {SpringApplication.run(Job4ScheduledApplication.class, args);}}

2、添加定时任务

定时任务的添加只需要使用@Scheduled注解标注即可,如果有多个定时任务可以创建多个@Scheduled注解标注的方法

@Component//@Component用于实例化类,将其类托管给 Spring 容器public class TaskJobUtil {/** * cron表达式:表示每2秒 执行任务 */@Scheduled(cron = "0/2 * * * * ?")public void task() {System.out.println("task0-start");sleep(5);System.out.println("task0-end");}/** * fixedRate:每间隔2秒执行一次任务 * 注意,默认情况下定时任务是在同一线程同步执行的,如果任务的执行时间(如5秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务 */@Scheduled(fixedRate = 2000)public void task0() {System.out.println("task0-start");sleep(5);System.out.println("task0-end");}/** * fixedDelay:每次延时2秒执行一次任务 * 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务 */@Scheduled(fixedDelay = 2000)public void task1() {System.out.println("task1-start");sleep(5);System.out.println("task1-end");}/** * initialDelay:首次任务启动的延时时间 */@Scheduled(initialDelay = 2000, fixedDelay = 3000)public void task2() {System.out.println("task2-start");sleep(5);System.out.println("task2-end");}private void sleep(long time) {try {TimeUnit.SECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}}}

CronTrigger(Cron触发器)功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表达式的

Cron 表达式是一个字符串,以5或6个空格隔开,分为6或7个域,每一个域代表一个含义

Cron的表达式被用来配置CronTrigger实例

从左到右分别为:秒、分、时、日期、月份、星期几、年份

具体参考博文:

https://blog.csdn.net/MinggeQingchun/article/details/125865778

Cron 在线生成

crontab执行时间计算 – 在线工具

quartz/Cron/Crontab表达式在线生成工具-BeJSON.com

[秒] [分] [时] [日期] [月] [星期] [秒] [分] [时] [日期] [月] [星期] [年]

*:表示任何时间触发任务 ,

:表示指定的时间触发任务

-:表示一段时间内触发任务

/:表示从哪一个时刻开始,每隔多长时间触发一次任务。

” />基于Spring Task实现定时任务

  • 优点:

    • 不需要依赖外部框架。
    • 简单快速实现任务。@EnableScheduling@Scheduled注解
  • 缺点:

    • 无法管理任务。要停止某个任务,必须重新发布。
    • 不支持动态调整。修改任务参数需要重启项目。
    • 不支持集群方式部署。集群模式下会出现任务多次被调度执行的情况,因为集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行

五、Quartz

除了JDK自带的API之外,我们还可以使用开源的框架来实现,比如Quartz

Quartz是Job scheduling(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会使用后者。使用Quartz可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午10点执行一次、每个月最后一天下午5点执行一次等。

Quartz架构图如下:

Quartz通常有三部分组成:调度器(Scheduler)、任务(JobDetail)、触发器(Trigger,包括SimpleTriggerCronTrigger

1、Job

定义具体要执行的任务

2、JobDetail

配置要执行任务的描述信息,即如何去定位要执行的Job,每次执行任务时,都会根据JobDetail创建一个Job对象,避免任务并发执行时访问同一个Job对象产生问题

jobdetail 就是对job的定义,而job是具体执行的逻辑内容。 具体的执行的逻辑需要实现 job类,并实现execute方法。如果使用jobdetail来定义,那么每次调度都会创建一个new job实例,这样带来的好处就是任务并发执行的时候,互不干扰,不会对临界资源造成影响

3、Trigger

触发器,配置任务执行的时间规则,需要和一个JobDetail关联起来

在 Quartz 中,trigger 是用于定义 Job 何时执行。当用 Scheduler 注册一个 Job 的时候要创建一个 Trigger 与这个 Job 相关联。

Quartz 中主要提供了四种类型的 Trigger:包括SimpleTrigger、CronTirgger//DateIntervalTrigger和 NthIncludedDayTrigger。这四种 trigger 可以满足企业应用中的绝大部分需求。

最常用的是 SimpleTrigger 和 CronTrigger

一般来说,如果你需要在一个固定的时间和重复次数或者一个固定的间隔时间,那么SimpleTrigger 比较合适; 如果你有许多复杂的作业调度,那么 CronTrigger 比较合适。

CronTrigger 和 Unix 的 cron 机制基本一样,基于通用的公历,我们需要的只是熟悉cron 表达式的用法。

关于Quartz中时间表达式的设置—–corn表达式:

withIdentity() 给触发器一些属性 比如名字,组名

startNow() 立刻启动

withSchedule(ScheduleBuilder schedBuilder) 以某种触发器触发

usingJobData(String dataKey, Boolean value) 给具体job传递参数

4、Scheduler

调度器,它维护了一个JobDetailTrigger的注册表,当任务关联的触发器到达预定的时间,调度器会去执行任务

Scheduler代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。

Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。 Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行,例如:如schedulerTest.scheduleJob(jobTest, triggerTest)。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。

Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例。

scheduler 由 scheduler 工厂创建:包括DirectSchedulerFactory 和 StdSchedulerFactory(STD:standard标准的意思)。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。

scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。

scheduler 除了启动外,scheduler 操作包括查询、设置 scheduler 为 standby 模式、继续、停止。 启动scheduler 非常简单,只需要调用 start() 方法即可。只有在scheduler 有实例或standby 模式才能调用start() 方法,一旦调用shutdown() 方法之后就不能再调用start() 方法。

(1)在Spring Boot中集成Quartz需要先添加如下Maven依赖

org.springframework.bootspring-boot-starter-quartz2.6.6

(2)在启动类添加@EnableScheduling注解来开启定时任务

(3)配置文件quartz.properties

#主要分为scheduler、threadPool、jobStore、dataSource等部分org.quartz.scheduler.instanceId=AUTOorg.quartz.scheduler.instanceName=DefaultQuartzScheduler#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略org.quartz.scheduler.rmi.export=false#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099org.quartz.scheduler.rmi.proxy=falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction=false#实例化ThreadPool时,使用的线程类为SimpleThreadPoolorg.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool#threadCount和threadPriority将以setter的形式注入ThreadPool实例#并发个数如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.#只有1到100之间的数字是非常实用的org.quartz.threadPool.threadCount=5#优先级 默认值为5org.quartz.threadPool.threadPriority=5#可以是“true”或“false”,默认为falseorg.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)org.quartz.jobStore.misfireThreshold=5000# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore#持久化方式,默认存储在内存中,此处使用数据库方式org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题org.quartz.jobStore.useProperties=true#表前缀org.quartz.jobStore.tablePrefix=QRTZ_#数据源别名,自定义org.quartz.jobStore.dataSource=qzDS#使用阿里的druid作为数据库连接池org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProviderorg.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz" />/** * 定时任务配置 ** @author hu.tj */@Configurationpublic class ScheduleConfig{@Beanpublic SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource){SchedulerFactoryBean factory = new SchedulerFactoryBean();factory.setDataSource(dataSource);// quartz参数Properties prop = new Properties();prop.put("org.quartz.scheduler.instanceName", "ZM Schedule");prop.put("org.quartz.scheduler.instanceId", "AUTO");// 线程池配置prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");prop.put("org.quartz.threadPool.threadCount", "20");prop.put("org.quartz.threadPool.threadPriority", "5");// JobStore配置prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");// 集群配置prop.put("org.quartz.jobStore.isClustered", "false");prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");// sqlserver 启用// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");prop.put("org.quartz.jobStore.misfireThreshold", "12000");prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");factory.setQuartzProperties(prop);factory.setSchedulerName("ZMScheduler");// 延时启动factory.setStartupDelay(1);factory.setApplicationContextSchedulerContextKey("applicationContextKey");// 可选,QuartzScheduler// 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了factory.setOverwriteExistingJobs(true);// 设置自动启动,默认为truefactory.setAutoStartup(true);return factory;}}

关于配置详细解释:任务调度框架Quartz(五)Quartz任务调度框架之最全Quartz系统参数配置详解_青山师的博客-CSDN博客

也可查看官网:

Quartz Documentation

1、定义Job

定义Job有两种方式,
第一种是直接定义任务类,并注册到Spring IoC容器中:

@Servicepublic class QuartzJobService {public void taskJob(){SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("job0-start:" + dateStr);try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("job0-end:" + dateStr);}}

第二种是继承QuartzJobBean,重写executeInternal方法,这种方式可以接受JobDetail传递的参数

public class QuartzJobDetail extends QuartzJobBean {@Overrideprotected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");String dateStr = sdf.format(new Date());System.out.println("job1-start:" + dateStr);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}// 获取参数JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();String date = jobDataMap.getString("date");System.out.println("参数:" + date);System.out.println("job1-end:" + dateStr);}}

这样就把JobDetail和我们之前定义的QuartzJob关联起来了。

2、配置JobDetail

JobDetail可以使用MethodInvokingJobDetailFactoryBean或者JobDetailFactoryBean配置,配置工作需要在一个Spring配置类中完成,可以定义一个QuartzConfig配置类,首先看MethodInvokingJobDetailFactoryBean的使用:

@Configurationpublic class QuartzConfig {/** * 配置JobDetail *///JobDetail可以使用MethodInvokingJobDetailFactoryBean配置@Beanpublic MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();// 指定任务类在IoC容器中的Bean名称bean.setTargetBeanName("quartzJobService");// 指定要执行的方法名称bean.setTargetMethod("taskJob");return bean;}}

这样就把JobDetail和之前QuartzJob所定义的任务关联起来了,接下来看JobDetailFactoryBean

@Configurationpublic class QuartzCronTriggerConfig {//JobDetail可以使用JobDetailFactoryBean配置@Beanpublic JobDetailFactoryBean jobDetailFactoryBean() {JobDetailFactoryBean bean = new JobDetailFactoryBean();// 指定任务类名称bean.setJobClass(QuartzJobDetail.class);// 准备参数JobDataMap jobDataMap = new JobDataMap();jobDataMap.put("date", "2020-08-08");// 传递参数bean.setJobDataMap(jobDataMap);return bean;}}

这样就把JobDetail和之前定义的QuartzJob2关联起来了,同时传递了参数。

3、配置Trigger

Trigger同样定义在QuartzConfig配置类里,常用的TriggerSimpleTriggerCronTrigger等,它们分别可以通过SimpleTriggerFactoryBeanCronTriggerFactoryBean来完成配置,我们先用SimpleTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail

@Configurationpublic class QuartzConfig {@Beanpublic SimpleTriggerFactoryBean simpleTriggerFactoryBean() {SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();bean.setRepeatCount(10);bean.setRepeatInterval(2000);// 关联JobDetailbean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());return bean;}}

SimpleTriggerFactoryBean的用法比较简单
再用CronTriggerFactoryBean配置的触发器关联JobDetailFactoryBean配置的JobDetail

@Configurationpublic class QuartzConfig {@Beanpublic CronTriggerFactoryBean cronTriggerFactoryBean() {CronTriggerFactoryBean bean = new CronTriggerFactoryBean();bean.setCronExpression("0/2 * * * * ? 2020");// 关联JobDetailbean.setJobDetail(jobDetailFactoryBean().getObject());return bean;}}

CronTriggerFactoryBean可以实现类似Spring中@Scheduledcron表达式的功能,同时支持了年份的配置。

4、配置Scheduler

最后一步就是通过SchedulerFactoryBean来配置Scheduler,来注册Trigger

@Configurationpublic class QuartzConfig {@Beanpublic SchedulerFactoryBean schedulerFactoryBean() {SchedulerFactoryBean bean = new SchedulerFactoryBean();// 注册两个Triggerbean.setTriggers(simpleTriggerFactoryBean().getObject(), cronTriggerFactoryBean().getObject());return bean;}}

Quartz会使用异步线程去执行定时任务,不会出现像@Scheduled中定时任务在同一线程同步执行的情况。

或者编写了Configuration 配置类

/** * 第一种:Simple类型 * 将该类标记为配置文件 * 创建 JobDetail * 创建 SimpleTrigger */@Configurationpublic class QuartzSimpleTriggerConfig {/** * 配置JobDetail *///JobDetail可以使用MethodInvokingJobDetailFactoryBean配置@Beanpublic MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();// 指定任务类在IoC容器中的Bean名称bean.setTargetBeanName("quartzJobService");// 指定要执行的方法名称bean.setTargetMethod("taskJob");return bean;}//通过SimpleTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail@Beanpublic SimpleTriggerFactoryBean simpleTriggerFactoryBean() {SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();bean.setRepeatCount(10);bean.setRepeatInterval(2000);// 关联JobDetailbean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());return bean;}/** * 配置Scheduler * *///通过SchedulerFactoryBean来配置Scheduler,来注册Trigger@Beanpublic SchedulerFactoryBean schedulerFactoryBean() {SchedulerFactoryBean bean = new SchedulerFactoryBean();// 注册两个Triggerbean.setTriggers(simpleTriggerFactoryBean().getObject());return bean;}}
/** * 第二种:Cron类型 * 将该类标记为配置文件 * 创建 JobDetail * 创建 CronTrigger */@Configurationpublic class QuartzCronTriggerConfig {//JobDetail可以使用JobDetailFactoryBean配置@Beanpublic JobDetailFactoryBean jobDetailFactoryBean() {JobDetailFactoryBean bean = new JobDetailFactoryBean();// 指定任务类名称bean.setJobClass(QuartzJobDetail.class);// 准备参数JobDataMap jobDataMap = new JobDataMap();jobDataMap.put("date", "2020-08-08");// 传递参数bean.setJobDataMap(jobDataMap);return bean;}/** * 配置Trigger *///通过CronTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail@Beanpublic CronTriggerFactoryBean cronTriggerFactoryBean() {CronTriggerFactoryBean bean = new CronTriggerFactoryBean();bean.setCronExpression("0/3 * * * * ? 2022");// 关联JobDetailbean.setJobDetail(jobDetailFactoryBean().getObject());return bean;}/** * 配置Scheduler * *///通过SchedulerFactoryBean来配置Scheduler,来注册Trigger@Beanpublic SchedulerFactoryBean schedulerFactoryBean() {SchedulerFactoryBean bean = new SchedulerFactoryBean();// 注册两个Triggerbean.setTriggers(cronTriggerFactoryBean().getObject());return bean;}}

非SpringBoot版

org.quartz-schedulerquartz2.3.2org.quartz-schedulerquartz-jobs2.3.2
public class TaskJob implements Job {@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {System.out.println(new Date() + " : 任务「TaskJob 」被执行");}}
public class MyScheduler {public static void main(String[] args) throws SchedulerException {// 1、创建调度器SchedulerSchedulerFactory schedulerFactory = new StdSchedulerFactory();Scheduler scheduler = schedulerFactory.getScheduler();// 2、创建JobDetail实例,并与PrintJob类绑定(Job执行内容)JobDetail jobDetail = JobBuilder.newJob(PrintJob.class).withIdentity("job", "group").build();// 3、构建Trigger实例,每隔1s执行一次Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup").startNow()//立即生效.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)//每隔1s执行一次.repeatForever()).build();//一直执行//4、Scheduler绑定Job和Trigger,并执行scheduler.scheduleJob(jobDetail, trigger);System.out.println("--------scheduler start ! ------------");scheduler.start();}}

六、Xxl-Job分布式任务调度平台

分布式任务调度平台XXL-JOB

XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用

xxl是xxl-job的开发者大众点评的【许雪里】名称的拼音开头

https://blog.csdn.net/MinggeQingchun/article/details/129883009

可参考

https://www.cnblogs.com/oeong/p/16212448.html

Java 定时任务-最简单的3种实现方法_深海呐的博客-CSDN博客_java定时任务

java定时任务_定时任务3种实现方式_IT枫斗者的博客-CSDN博客_java定时任务

https://www.jb51.net/article/226802.htm#_label4