定时任务总结
工作中总是要用到定时任务,先总结如下:
一、java jdk自带的定时任务
他主要由两个要素构成,是一个job和一个timer,job是执行业务逻辑的,timer是负责调度定时设定的
job
1 /** 2 * 3 */ 4 package timer; 5 6 import java.text.SimpleDateFormat; 7 import java.util.TimerTask; 8 9 /** 10 * @author 醉逍遥 11 * 12 */ 13 public class MyTimerTask extends TimerTask{ 14 15 private String name; 16 17 public MyTimerTask(String name) { 18 this.name = name; 19 } 20 21 @Override 22 public void run() { 23 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 24 System.out.println("current time is: "+sdf.format(System.currentTimeMillis())); 25 26 } 27 28 public String getName() { 29 return name; 30 } 31 32 public void setName(String name) { 33 this.name = name; 34 } 35 }
timer
1 /** 2 * 3 */ 4 package timer; 5 6 import java.text.SimpleDateFormat; 7 import java.util.Calendar; 8 import java.util.Date; 9 import java.util.Timer; 10 11 /** 12 * @author 醉逍遥 13 * 14 */ 15 public class MyTimer { 16 17 public static void main(String[] args) throws InterruptedException { 18 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 19 Timer timer = new Timer(); 20 MyTimerTask task = new MyTimerTask("test"); 21 // timer.schedule(task, 3000); 22 Calendar canlendar = Calendar.getInstance(); 23 /* Date time = canlendar.getTime(); 24 System.out.println("first time: "+sdf.format(time)); 25 canlendar.add(Calendar.SECOND,3); 26 System.out.println("second time: "+sdf.format(canlendar.getTime())); 27 // timer.schedule(task, 3000, 10000); 28 // timer.scheduleAtFixedRate(task, 2000, 2000); 29 timer.scheduleAtFixedRate(task, canlendar.getTime(), 2000);*/ 30 31 // 注意:在格里高利历和罗马儒略历中一年中的第一个月是 JANUARY,它为 0;最后一个月取决于一年中的月份数。 32 // 所以这个值的初始值为0,所以我们用它来表示日历月份时需要加1 33 canlendar.set(2019, 1, 27, 15, 01, 40); 34 System.out.println(sdf.format(canlendar.getTime())); 35 timer.schedule(task, canlendar.getTime(), 3000); 36 } 37 38 }
二、quartz
quartz的三要素主要有:
- Scheduler:调度器。所有的调度都是由它控制。
- Trigger: 定义触发的条件。它的类型是SimpleTrigger和cronTrigger。
- JobDetail & Job: JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中, 为什么设计成JobDetail + Job,不直接使用Job?这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
代码,首先创建maven工程导入jar包
<!-- 定时任务框架 --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
job
1 /** 2 * 3 */ 4 package com.sharp.forward; 5 6 import java.text.SimpleDateFormat; 7 8 import org.quartz.Job; 9 import org.quartz.JobDataMap; 10 import org.quartz.JobDetail; 11 import org.quartz.JobExecutionContext; 12 import org.quartz.JobExecutionException; 13 import org.quartz.JobKey; 14 import org.quartz.Trigger; 15 import org.quartz.TriggerKey; 16 17 /** 18 * @author 醉逍遥 19 * 20 */ 21 public class TestJob implements Job { 22 23 @Override 24 public void execute(JobExecutionContext context) throws JobExecutionException { 25 // JobExecutionContext 可以获取各种传递过来的参数进行业务逻辑的处理 26 JobDetail jobDetail = context.getJobDetail(); 27 JobDataMap map = jobDetail.getJobDataMap(); 28 JobDataMap map1 = context.getMergedJobDataMap(); 29 System.out.println(map1.get("key1"));//合并的JobDataMap相同key第二个会覆盖第一个 30 System.out.println(map.getBoolean("isTrue")); 31 System.out.println(""+jobDetail.getJobDataMap().getString("key1"));//未合并的JobDataMap相同key显示的是第一个 32 String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis()); 33 System.out.println("currentTime: "+currentTime); 34 System.out.println("******************************"); 35 // 通过jobDetail获取的key和通过trigger获取的jobkey是一样的 36 Trigger trigger = context.getTrigger(); 37 JobKey jobKey = trigger.getJobKey(); 38 JobKey jobKey1 = jobDetail.getKey(); 39 System.out.println("jobDetail name: "+jobKey.getName()+"\ttrigger name: "+jobKey.getName()); 40 System.out.println("jobDetail class: "+jobKey.getClass()+"\ttrigger class: "+jobKey.getClass()); 41 System.out.println("jobDetail group: "+jobKey.getGroup()+"\ttrigger group: "+jobKey.getGroup()); 42 // 获取trigger的相关key 43 TriggerKey triggerKey = trigger.getKey(); 44 System.out.println("trigger name: "+triggerKey.getName()); 45 System.out.println("trigger class: "+triggerKey.getClass()); 46 System.out.println("trigger group: "+triggerKey.getGroup()); 47 } 48 49 }
主代码
1 /** 2 * 3 */ 4 package com.sharp.forward; 5 6 import java.util.Calendar; 7 import java.util.concurrent.TimeUnit; 8 9 import org.quartz.JobBuilder; 10 import org.quartz.JobDetail; 11 import org.quartz.Scheduler; 12 import org.quartz.SchedulerException; 13 import org.quartz.SchedulerFactory; 14 import org.quartz.SimpleScheduleBuilder; 15 import org.quartz.Trigger; 16 import org.quartz.TriggerBuilder; 17 import org.quartz.impl.StdSchedulerFactory; 18 19 /** 20 * @author 醉逍遥 21 * 22 */ 23 public class QuartzTest { 24 25 /** 26 * @param args 27 * @throws InterruptedException 28 */ 29 public static void main(String[] args) throws InterruptedException { 30 try { 31 // **************要素一 scheduler*********************** 32 // 创建scheduler方法一 33 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); 34 // 创建scheduler方法二 35 // SchedulerFactory factory = new StdSchedulerFactory(); 36 // Scheduler scheduler = factory.getScheduler(); 37 // **************要素二 JobDetail & Job*********************** 38 JobDetail jobDetail = JobBuilder.newJob(TestJob.class).withIdentity("job1", "group1") 39 .usingJobData("key1", "my first jobDetial").usingJobData("double value", 9.99).build(); 40 // **************要素三 trigger*********************** 41 Calendar calendar = null; 42 Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group0").usingJobData("isTrue", false) 43 .usingJobData("key1", "my fist trigger") 44 .startNow() 45 .withSchedule(//设定规则 46 SimpleScheduleBuilder.simpleSchedule() 47 .withIntervalInSeconds(3) 48 .repeatForever() 49 ) 50 .build(); 51 // 执行 52 scheduler.scheduleJob(jobDetail,trigger); 53 System.out.println("task start-----"); 54 scheduler.start(); 55 System.out.println("task end ======"); 56 TimeUnit.MILLISECONDS.sleep(20000); 57 scheduler.shutdown(); 58 System.out.println("scheduler shutdown"); 59 }catch (SchedulerException e) { 60 // TODO Auto-generated catch block 61 e.printStackTrace(); 62 } 63 } 64 65 }
运行结果
10:07:52.466 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor 10:07:52.471 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main 10:07:52.483 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl 10:07:52.483 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.0 created. 10:07:52.484 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized. 10:07:52.485 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.0) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED' Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally. NOT STARTED. Currently in standby mode. Number of jobs executed: 0 Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads. Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered. 10:07:52.485 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties' 10:07:52.485 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.0 task start----- 10:07:52.491 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started. task end ====== 10:07:52.491 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:07:52.493 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.job1', class=com.sharp.forward.TestJob 10:07:52.497 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:07:52.497 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.job1 my fist trigger false my first jobDetial currentTime: 2019-02-28 10:07:52 ****************************** jobDetail name: job1 trigger name: job1 jobDetail class: class org.quartz.JobKey trigger class: class org.quartz.JobKey jobDetail group: group1 trigger group: group1 trigger name: trigger1 trigger class: class org.quartz.TriggerKey trigger group: group0 10:07:55.488 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.job1', class=com.sharp.forward.TestJob 10:07:55.489 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers 10:07:55.489 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.job1 my fist trigger false my first jobDetial currentTime: 2019-02-28 10:07:55 ****************************** jobDetail name: job1 trigger name: job1 jobDetail class: class org.quartz.JobKey trigger class: class org.quartz.JobKey jobDetail group: group1 trigger group: group1 trigger name: trigger1 trigger class: class org.quartz.TriggerKey trigger group: group0
一些详细的概念性介绍,可以去https://www.cnblogs.com/zhanghaoliang/p/7886110.html里面看,写的比较详细,在这里只贴上一些主要的概念讲解有利于理解,其他不再累述。
Quartz的三个基本要素
Quartz对任务调度的领域问题进行了高度的抽象,提出了调度器、任务和触发器这3个核心的概念,并在org.quartz通过接口和类对重要的这些核心概念进行描述:
●Job:是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
●JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
通过该类的构造函数可以更具体地了解它的功用:JobDetail(java.lang.String name, java.lang.String group, java.lang.Class jobClass),该构造函数要求指定Job的实现类,以及任务在Scheduler中的组名和Job名称;
●Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;
●Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。
假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。针对不同时间段类型,Quartz在org.quartz.impl.calendar包下提供了若干个Calendar的实现类,如AnnualCalendar、MonthlyCalendar、WeeklyCalendar分别针对每年、每月和每周进行定义;
●Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;
●ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。
如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
Trigger自身也可以拥有一个JobDataMap,其关联的Job可以通过JobExecutionContext#getTrigger().getJobDataMap()获取Trigger中的JobDataMap。不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。
Quartz拥有完善的事件和监听体系,大部分组件都拥有事件,如任务执行前事件、任务执行后事件、触发器触发前事件、触发后事件、调度器开始事件、关闭事件等等,可以注册相应的监听器处理感兴趣的事件。
quartz在spring中的配置及解释:
xml文档:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource"> <ref bean="datasource"/> <!--数据源引用指向--> </property> <property name="applicationContextSchedulerContextKey" value="applicationContextKey"/> <!--applicationContextSchedulerContextKey: 是org.springframework.scheduling.quartz.SchedulerFactoryBean这个类中
把spring上下 文以key/value的方式存放在了quartz的上下文中了,
可以用applicationContextSchedulerContextKey所定义的key得到对应的spring上下文--> <property name="configLocation" value="classpath:quartz.properties"/> <!--quartz的配置文件的位置 --> <property name="triggers"> <list> <ref bean="trigger1"/> </list> </property> </bean> <bean id="jobDetail1" class="org.springframework.scheduling.quartz.JobDetailBean"> <property name="jobClass"> <value>继承QuartzJobBean的类的引用,如果不继承QuartzJobBean可以参考 http://www.javaeye.com/topic/486055</value> </property> </bean> <bean id="trigger1" class="org.springframework.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="jobDetail1"/> <property name="cronExpression" value="0 0/5 * ? * * *"/> </bean> </beans>
在job中可以通过scheduler去得spring上下文信息:
@Override public void execute(JobExecutionContext context) throws JobExecutionException { ApplicationContext applicationContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext"); }