【java】定时器
定时器的实现方式:
1、线程等待实现
最原始最简单的方式,先创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。
public class Task { public static void main(String[] args) { // run in a second final long timeInterval = 1000; Runnable runnable = new Runnable() { @Override public void run() { while (true) { System.out.println("Hello !!"); try { Thread.sleep(timeInterval); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread thread = new Thread(runnable); thread.start(); } }
2、JDK自带Timer实现
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();
在指定延迟时间后执行一次,这类是比较常见的场景,比如:当系统初始化某个组件之后,延迟几秒中,然后进行定时任务的执行。
public class DoSomethingTimerTask extends TimerTask { private String taskName; public DoSomethingTimerTask(String taskName) { this.taskName = taskName; } @Override public void run() { System.out.println(new Date() + " : 任务「" + taskName + "」被执行。"); } }
public class DelayOneDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new DoSomethingTimerTask("DelayOneDemo"),1000L); } }
Timer的缺陷:
Timer计时器可以定时(指定时间执行任务)、延迟(延迟5秒执行任务)、周期性地执行任务(每隔个1秒执行任务)。但是,Timer存在一些缺陷。首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。
其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,它会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。
3、JDK自带ScheduledExecutorService实现
ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行。也就是说,任务是并发执行,互不影响。
需要注意:只有当执行调度任务时,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态。
ScheduledExecutorService主要有以下4个方法:
// 在指定延迟时间后执行一个 Runnable 任务。因为是 Runnable ,返回值 ScheduledFuture.get() 返回值为 null。 ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit); // 在指定延迟时间后执行一个 Callable 任务。因为是 Callable ,所以 ScheduledFuture.get() 有返回值。 <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit); // 创建并执行一个在给定初始延迟 (initialDelay) 后首次启动执行的定时任务,任务执行具有给定的周期 (period); // 也就是将在 initialDelay 后开始执行,然后在 initialDelay + period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。 // 如果任务的任何一个执行遇到异常,则后续执行都会被取消。相反,任务正常执行的话,只能通过线程池的取消或终止操作来终止该任务。 // 如果此任务的任何一个执行要花费比其周期更长的时间,则后续的执行将会被推迟,不会出现两个任务同时执行。 ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit); // 创建并执行一个在给定初始延迟 (initialDelay) 后首次启动执行的任务,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟(delay)。 // 如果任务的任一执行遇到异常,就会取消后续任务执行。相反,任务正常执行的话,只能通过线程池的取消或终止操作来终止该任务。 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);
其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时比较方便,运用的也比较多。
ScheduledExecutorService中定义的这四个接口方法和Timer中对应的方法几乎一样,只不过Timer的scheduled方法需要在外部传入一个TimerTask的抽象任务。
而ScheduledExecutorService封装的更加细致了,传Runnable或Callable内部都会做一层封装,封装一个类似TimerTask的抽象任务类(ScheduledFutureTask)。然后传入线程池,启动线程去执行该任务。
4、Quartz框架实现
Quartz是Job scheduling(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会使用后者。使用Quartz可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午10点执行一次、每个月最后一天下午5点执行一次等。
Quartz通常有三部分组成:调度器(Scheduler)、任务(JobDetail)、触发器(Trigger,包括SimpleTrigger和CronTrigger)。下面以具体的实例进行说明。
要使用Quartz,首先需要在项目的pom文件中引入相应的依赖:
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.3.2</version> </dependency>
5、Spring Task实现
从Spring 3开始,Spring自带了一套定时任务工具Spring-Task,可以把它看成是一个轻量级的Quartz,使用起来十分简单,除Spring相关的包外不需要额外的包,支持注解和配置文件两种形式。通常情况下在Spring体系内,针对简单的定时任务,可直接使用Spring提供的功能。
基于XML配置文件的形式就不再介绍了,直接看基于注解形式的实现。代码示例:
@Component("taskJob") public class TaskJob { @Scheduled(cron = "0 0 3 * * ?") public void job1() { System.out.println("通过cron定义的定时任务"); } @Scheduled(fixedDelay = 1000L) public void job2() { System.out.println("通过fixedDelay定义的定时任务"); } @Scheduled(fixedRate = 1000L) public void job3() { System.out.println("通过fixedRate定义的定时任务"); } }
如果是在Spring Boot项目中,需要在启动类上添加@EnableScheduling来开启定时任务。
上述代码中,@Component用于实例化类,这个与定时任务无关。@Scheduled指定该方法是基于定时任务进行执行,具体执行的频次是由cron指定的表达式所决定。关于cron表达式上面CronTrigger所使用的表达式一致。与cron对照的,Spring还提供了fixedDelay和fixedRate两种形式的定时任务执行。
fixedDelay和fixedRate的区别:
fixedDelay和fixedRate的区别于Timer中的区别很相似。
fixedRate有一个时刻表的概念,在任务启动时,T1、T2、T3就已经排好了执行的时刻,比如1分、2分、3分,当T1的执行时间大于1分钟时,就会造成T2晚点,当T1执行完时T2立即执行。
fixedDelay比较简单,表示上个任务结束,到下个任务开始的时间间隔。无论任务执行花费多少时间,两个任务间的间隔始终是一致的。
6、Spring定时任务接口 SchedulingConfigurer 实现
Spring 中,创建定时任务除了使用@Scheduled 注解外,还可以使用 SchedulingConfigurer。
@Schedule 注解有一个缺点,其定时的时间不能动态的改变,而基于 SchedulingConfigurer 接口的方式可以做到。
SchedulingConfigurer 接口可以实现在@Configuration 类上,同时不要忘了,还需要@EnableScheduling 注解的支持。
该接口的实现方法如下:
public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
其中 ScheduledTaskRegistrar 类的方法有下列几种:
从方法的命名上可以猜到,方法包含定时任务,延时任务,基于 Cron 表达式的任务,以及 Trigger 触发的任务。
下面演示了使用方法:
import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.config.TriggerTask; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; import java.util.Date; @Component @EnableScheduling public class SpringSchedulingConfigurerTest01 implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 默认的,SchedulingConfigurer 使用的也是单线程的方式,如果需要配置多线程,则需要指定 PoolSize // ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); // taskScheduler.setPoolSize(10); // taskScheduler.initialize(); // taskRegistrar.setTaskScheduler(taskScheduler); Runnable task01 = new Runnable() { @Override public void run() { System.out.println("执行定时任务1: " + new Date()); } }; // 添加固定日期任务 taskRegistrar.addFixedRateTask(task01, 1000); Runnable task02 = new Runnable() { @Override public void run() { System.out.println("执行定时任务2: " + new Date()); } }; Trigger trigger = new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { return new CronTrigger("0/2 * * * * ?") .nextExecutionTime(triggerContext); } }; // 添加任务+任务触发器(表达式) taskRegistrar.addTriggerTask(task02, trigger); TriggerTask triggrtTask03 = new TriggerTask(new Runnable() { @Override public void run() { System.out.println("执行定时任务3: " + new Date()); } }, new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { // 返回执行周期(Date),cron表达式 return new CronTrigger("0/3 * * * * ?") .nextExecutionTime(triggerContext); } }); // 添加触发任务(合在一起添加) taskRegistrar.addTriggerTask(triggrtTask03); } }
参考:Spring 中,定时任务接口 SchedulingConfigurer - 腾讯云开发者社区-腾讯云 (tencent.com)
本文来自博客园,作者:日月星宿,转载请注明原文链接:https://www.cnblogs.com/ryxxtd/p/17371056.html