A timing task is a function of specifying a future time range to perform certain tasks. In current WEB applications, most applications have task scheduling functions. They have their own syntax and solutions for different voices and operating systems. The Windows operating system calls it task planning, and the cron services in Linux provide this function. This function is often involved in our business development system. This chat will use the Java language to complete the use of commonly used timing tasks in daily development work, hoping to help everyone's work and study.
1. Timed task scenarios
(1) Drive processing workflow
As a new prepaid order, it is initialized and placed. If the order is not paid within the specified time, it will be considered that the timeout order will be closed. It is widely used in the e-commerce system. Users purchase goods and generate orders but do not make payments. If the order is not paid within 30 minutes, the order will be closed (and the number of people meeting this scenario is huge), and it is impossible to use manual intervention.
(2) System maintenance
The dispatch work will obtain the system exception log and store some key point data in the database. It will be dumped to the database at 11:30 PM every weekday (except holidays and weekdays), and generate an XML file and send it to the email address of a certain employee.
(3) Provide reminder services within the application.
The system regularly reminds the logged-in user to perform related work at a certain time.
(4) Timed reconciliation tasks
The company and the three-party companies (operators, banks, etc.) will conduct reconciliation of the business on the same day after midnight every day, send the reconciliation information result data to the email address of the relevant person in charge, and process the mismatch data during the working hours of the next day.
(5) Data statistics
There are many data records, and real-time reading and querying from the database will generate a certain amount of time, which is for customer experience and performance needs. Therefore, the data is summarized every week (days, hours), so that the data can be quickly presented when displaying the data.
There are many scenarios where timing tasks are used... It seems that timing tasks are really widely used in our daily development...
2. Explanation of mainstream timing tasks technology Timer
I believe everyone is already very familiar with java.util.Timer. It is the easiest way to implement task scheduling. Here is a specific example:
package com.ibm.scheduler; import java.util.Timer; import java.util.TimerTask; public class TimerTest extends TimerTask { private String jobName = ""; public TimerTest(String jobName) { super(); this.jobName = jobName; } @Override public void run() { System.out.println("execute " + jobName); } public static void main(String[] args) { Timer timer = new Timer(); long delay1 = 1 * 1000; long period1 = 1000; // After 1 second from now, execute job1 timer.schedule(new TimerTest("job1"), delay1, period1); long delay2 = 2 * 1000; long period2 = 2000; // After 2 seconds from now, execute job2 timer.schedule(new TimerTest("job2"), delay2, period2); } } /**
Output result:
execute job1
execute job1
execute job2
execute job1
execute job1
execute job2
*/
The core classes that use Timer to implement task scheduling are Timer and TimerTask. The Timer is responsible for setting the start and interval execution time of TimerTask. The user only needs to create an inheritance class of TimerTask, implement its own run method, and then throw it to the Timer for execution. The design core of Timer is a TaskList and a TaskThread. Timer throws the received tasks into its TaskList, which sorts the TaskList according to the initial execution time of the Task. TimerThread starts to become a daemon thread when creating Timer. This thread polls all tasks, finds a task that is recently to be executed, and then sleeps. When the start time of the most recently to be executed, TimerThread is awakened and the task is executed. After that, TimerThread updates the most recent task to be executed and continues to hibernate.
The advantage of Timer is that it is simple and easy to use, but since all tasks are scheduled by the same thread, all tasks are executed serially, and only one task can be executed at the same time. The delay or exception of the previous task will affect the subsequent tasks (this point needs to be paid attention to).
ScheduledExecutor
In view of the above shortcomings of Timer, Java 5 has launched ScheduledExecutor based on thread pool design. The design idea is that each scheduled task will be executed by a thread in the thread pool, so the tasks are executed concurrently and will not be disturbed by each other. It should be noted that ScheduedExecutor will only really start a thread when the execution time of the task comes, and the ScheduedExecutor is polling the task for the rest of the time.
package com.ibm.scheduler;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class ScheduledExecutorTest implements Runnable { private String jobName = ""; public ScheduledExecutorTest(String jobName) { super(); this.jobName = jobName; } @Override public void run() { System.out.println("execute " + jobName); } public static void main(String[] args) { ScheduledExecutorService service = Executors.newScheduledThreadPool(10); long initialDelay1 = 1; long period1 = 1; // After 1 second from now on, execute job1 service.scheduleAtFixedRate( new ScheduledExecutorTest("job1"), initialDelay1, period1, TimeUnit.SECONDS); long initialDelay2 = 1; long delay2 = 1; // After 2 seconds from now on, execute job2 service.scheduleWithFixedDelay( new ScheduledExecutorTest("job2"), initialDelay2, delay2, TimeUnit.SECONDS); }}/**Output result: execute job1execute job1execute job2execute job1execute job1execute job2*/
The above code shows two most commonly used scheduling methods, ScheduleAtFixedRate and ScheduleWithFixedDelay, in ScheduleDelta. ScheduleAtFixedRate Each execution time is a time interval pushing back from the beginning of the previous task, that is, each execution time is: initialDelay, initialDelay+period, initialDelay+2*period, … ScheduleWithFixedDelay Each execution time is a time interval pushing back from the end of the previous task, that is, each execution time is: initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay. It can be seen that ScheduleAtFixedRate is based on fixed time intervals for task scheduling, and ScheduleWithFixedDelay depends on the length of each task execution time and is based on unfixed time intervals for task scheduling.
Use ScheduledExecutor and Calendar to implement complex task scheduling
Both Timer and ScheduledExecutor can only provide task scheduling based on the start time and repetition interval, and are not competent for more complex scheduling requirements. For example, set the task to be executed every Tuesday at 16:38:10. This function cannot be directly implemented using either Timer or ScheduledExecutor, but we can implement it indirectly with the help of Calendar.
package com.ibm.scheduler;import java.util.Calendar;import java.util.Date;import java.util.TimerTask;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class ScheduledExceutorTest2 extends TimerTask { private String jobName = ""; public ScheduledExceutorTest2(String jobName) { super(); this.jobName = jobName; } @Override public void run() { System.out.println("Date = "+new Date()+", execute " + jobName); } /** * Calculate the latest time that starts from the current time currentDate and meets the conditions dayOfWeek, hourOfDay, * minuteOfHour, secondOfMinite* @return */ public Calendar getEarliestDate(Calendar currentDate, int dayOfWeek, int hourOfDay, int minuteOfHour, int secondOfMinite) { //Calculate the values of WEEK_OF_YEAR, DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND, etc. int currentWeekOfYear = currentDate.get(Calendar.WEEK_OF_YEAR); int currentDayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK); int currentHour = currentDate.get(Calendar.HOUR_OF_DAY); int currentMinute = currentDate.get(Calendar.MINUTE); int currentSecond = currentDate.get(Calendar.SECOND); //If the dayOfWeek in the input condition is less than the dayOfWeek of the current date, then WEEK_OF_YEAR needs to be postponed by one week boolean weekLater = false; if (dayOfWeek < currentDayOfWeek) { weekLater = true; } else if (dayOfWeek == currentDayOfWeek) { //When the input condition is equal to the dayOfWeek of the current date, if //hourOfDay in the input condition is less than //currentHour of the current date, WEEK_OF_YEAR needs to be postponed by one week if (hourOfDay < currentHour) { weekLater = true; } else if (hourOfDay == currentHour) { //When the input condition is equal to the dayOfWeek, hourOfDay of the current date, //If the minuteOfHour in the input condition is less than //currentMinute of the current date, WEEK_OF_YEAR needs to be postponed by one week if (minuteOfHour < currentMinute) { weekLater = true; } else if (minuteOfHour == currentSecond) { //When the input condition is equal to the dayOfWeek, hourOfDay, //minuteOfHour, if //secondOfMinite in the input condition is less than the currentSecond of the current date, //Then WEEK_OF_YEAR needs to be postponed by one week if (secondOfMinite < currentSecond) { weekLater = true; } } } } } if (weekLater) { //Set WEEK_OF_YEAR in the current date to postpone one week currentDate.set(Calendar.WEEK_OF_YEAR, currentWeekOfYear + 1); } // Set DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND in the current date as the values in the input condition. currentDate.set(Calendar.DAY_OF_WEEK, dayOfWeek); currentDate.set(Calendar.HOUR_OF_DAY, hourOfDay); currentDate.set(Calendar.MINUTE, minuteOfHour); currentDate.set(Calendar.SECOND, secondOfMinite); return currentDate; } public static void main(String[] args) throws Exception { ScheduledExceutorTest2 test = new ScheduledExceutorTest2("job1"); //Get the current time Calendar currentDate = Calendar.getInstance(); long currentDateLong = currentDate.getTime().getTime(); System.out.println("Current Date = " + currentDate.getTime().toString()); //Calculate the last execution time that meets the condition Calendar earlierDate = test .getEarliestDate(currentDate, 3, 16, 38, 10); long earlierDateLong = earlyDate.getTime().getTime(); System.out.println("Earliest Date = " + earlierDate.getTime().toString()); //Calculate the time interval from the current time to the last execution time long delay = earlierDateLong - currentDateLong; //Calculate the execution period is one week long period long period = 7 * 24 * 60 * 60 * 1000; ScheduledExecutorService service = Executors.newScheduledThreadPool(10); //From now delay milliseconds, execute job1 service.scheduleAtFixedRate(test, delay, period, TimeUnit.MILLISECONDS); }}/**Output result:Current Date = Wed Feb 02 17:32:01 CST 2011Earliest Date = Tue Feb 8 16:38:10 CST 2011Date = Tue Feb 8 16:38:10 CST 2011, execute job1Date = Tue Feb 15 16:38:10 CST 2011, execute job1*/
The above code implements the function of scheduling tasks at 16:38:10 every Tuesday. The core is to calculate the absolute time of 16:38:10 on the last Tuesday based on the current time, and then calculate the time difference from the current time as a parameter to call the ScheduledExceutor function. To calculate the latest time, the function of java.util.calendar is used. First, we need to explain some design ideas of calendar. Calendar has the following combinations that uniquely identify a date:
Quote
YEAR + MONTH + DAY_OF_MONTH
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
YEAR + DAY_OF_YEAR
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
The above combinations are combined with HOUROFDAY + MINUTE + SECOND to be a complete time mark.
The above DEMO adopts the last combination method. The input is DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND and the current date, and the output is a future date that satisfies DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND and is closest to the current date. The principle of calculation is to start comparison from the input DAY_OF_WEEK. If it is less than the DAY_OF_WEEK of the current date, you need to further increase the WEEK_OF_YEAR, that is, add the WEEK_OF_YEAR in the current date and overwrite the old value; if it is equal to the current DAY_OF_WEEK, continue to compare HOUR_OF_DAY; if it is greater than the current DAY_OF_WEEK, directly call the calendar.set(field, value) function of java.util.calenda to assign the DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND to the input value, and so on until the comparison is reached to SECOND. We can select different combinations based on the input requirements to calculate the latest execution time.
It is quite cumbersome to implement the task scheduling using the above method. We hope to need a more complete task scheduling tool to solve these complex scheduling problems. Fortunately, the open source toolkit Quartz has shown great capabilities in this regard.
Quartz
OpenSymphony Open Source Organization is another open source project in the field of Job scheduling, which can be combined with J2EE and J2SE applications or used alone. Quartz can be used to create simple or complex programs that run ten, hundreds, or even tens of thousands of jobs.
Let’s take a look at an example:
package com.test.quartz;import static org.quartz.DateBuilder.newDate;import static org.quartz.JobBuilder.newJob;import static org.quartz.SimpleScheduleBuilder.simpleSchedule;import static org.quartz.TriggerBuilder.newTrigger;import java.util.GregorianCalendar;import org.quartz.JobDetail;import org.quartz.Scheduler;import org.quartz.Trigger;import org.quartz.impl.StdSchedulerFactory;import org.quartz.impl.calendar.AnnualCalendar;public class QuartzTest { public static void main(String[] args) { try { //Create scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //Define a Trigger Trigger trigger = newTrigger().withIdentity("trigger1", "group1") //Define name/group .startNow()//Once scheduler is added, it takes effect immediately.withSchedule(simpleSchedule() //Use SimpleTrigger .withIntervalInSeconds(1) //Execute every second.repeatForever()) //Statually execute, rushing until it is still not stopped.build(); //Define a JobDetail JobDetail job = newJob(HelloQuartz.class) //Define the Job class as HelloQuartz class, which is the real execution logic.withIdentity("job1", "group1") //Define name/group .usingJobData("name", "quartz") //Define the attribute.build(); //Add to this scheduler.scheduleJob(job, trigger); //Start scheduler.start(); //Closed Thread.sleep(10000); scheduler.shutdown(true); } catch (Exception e) { e.printStackTrace(); } }}package com.test.quartz;import java.util.Date;import org.quartz.DisallowConcurrentExecution;import org.quartz.Job;import org.quartz.JobDetail;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;public class HelloQuartz implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobDetail detail = context.getJobDetail(); String name = detail.getJobDataMap().getString("name"); System.out.println("say hello to " + name + " at " + new Date()); }}Through the above examples: The 3 most important basic elements of Quartz:
•Scheduler: Scheduler. All scheduling is controlled by it.
•Trigger: Defines the triggering condition. In the example, its type is SimpleTrigger, which is executed every 1 second (what is SimpleTrigger, will be described in detail below).
•JobDetail & Job: JobDetail defines task data, and the real execution logic is in the job, in the example, HelloQuartz. Why is it designed as a JobDetail + Job, and not directly use Job? This is because it is possible to execute tasks concurrently. If Scheduler uses jobs directly, there will be a problem of concurrent access to the same Job instance. In the JobDetail & Job method, every time the sheduler executes, it will create a new Job instance based on the JobDetail, so that the problem of concurrent access can be avoided.
Quartz API
Quartz's API style is after 2.x, and adopts the DSL style (usually means fluent interface style), which is the newTrigger() part in the example. It is implemented through Builder, which is the following. (Most of the following codes refer to these Builders)
//job-related builderport static org.quartz.JobBuilder.*;//trigger-related builderport static org.quartz.TriggerBuilder.*;import static org.quartz.SimpleScheduleBuilder.*;import static org.quartz.CronScheduleBuilder.*;import static org.quartz.DailyTimeIntervalScheduleBuilder.*;import static org.quartz.CalendarIntervalScheduleBuilder.*;//date-related builderport static org.quartz.DateBuilder.*;DSL style will be more coherent and refreshing to write, and since it is not the style of using setter, it will be easier to understand semantically. Compare: JobDetail jobDetail=new JobDetailImpl("jobDetail1","group1",HelloQuartz.class);jobDetail.getJobDataMap().put("name", "quartz");SimpleTriggerImpl trigger=new SimpleTriggerImpl("trigger1","group1");trigger.setStartTime(new Date());trigger.setRepeatInterval(1);trigger.setRepeatCount(-1);About name and group
JobDetail and Trigger have name and group.
name is their unique identifier in this sheduler. If we want to update a JobDetail definition, we just need to set a JobDetail instance with the same name.
group is an organization unit, and sheduler will provide some APIs for the entire group of operations, such as scheduler.resumeJobs().
Trigger
Before starting to explain each Trigger in detail, you need to understand some commonalities of Trigger.
StartTime & EndTime
The time interval specified by startTime and endTime will be triggered. Outside this interval, Trigger will not be triggered. All Triggers will contain these two properties.
Priority
When the scheduler is busy, multiple Triggers may be triggered at the same time, but the resources are insufficient (such as insufficient thread pool). Then, at this time, a better way than a scissors stone cloth is to set priority. Execute the priority first. It should be noted that priority will only work between Triggers executed at the same time, if one Trigger is 9:00 and the other Trigger is 9:30. Then no matter how high the next priority is, the previous one will be executed first. The default value of priority is 5, and the default value is used when it is negative. The maximum value does not seem to be specified, but it is recommended to follow the Java standard and use 1-10. Otherwise, if you know if there is a larger value on it when you see [priority is 10].
Misfire (miss trigger) strategy
When similar Scheduler resources are insufficient, or when the machine crashes and restarts, it is possible that some Triggers are not triggered at the time when they should be triggered, that is, Miss Fire. At this time, Trigger needs a strategy to handle this situation. The optional strategies for each Trigger vary. Here are two points to pay attention to:
MisFire trigger has a threshold value, which is configured in the JobStore. More than RAMJobStore is org.quartz.jobStore.misfireThreshold. MisFire will only be counted as exceeding this threshold. Less than this threshold, Quartz will be retriggered. All MisFire strategies actually answer two questions:
•Does that have MisFire have to be re-triggered?
•If MisFire occurs, do you want to adjust the existing scheduling time?
For example, SimpleTrigger's MisFire strategy includes:
•MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY This does not mean ignoring the missed trigger, but rather ignoring the MisFire policy. It will retrieve all MisFire tasks when the resource is appropriate and will not affect the existing scheduling time. For example, SimpleTrigger executes every 15 seconds, and it has 5 minutes in the middle and misses 20 missing. After 5 minutes, assuming that resources are sufficient and the task allows concurrency, it will be triggered at one time. This property is applicable to all Triggers.
•MISFIRE_INSTRUCTION_FIRE_NOW Ignore tasks that have been MisFire and perform schedules immediately. This usually only applies to tasks that are only performed once.
•MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT Set the startTime to the current time and immediately reschedule the task, including MisFire.
•MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT Similar to MISFIREINSTRUCTIONRESCHEDULENOWWITHEXISTINGREPEAT_COUNT, the difference is that tasks that have already been MisFire will be ignored.
•MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT At the next scheduled time, restart the dispatch task, including MisFire.
•MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT Similar to MISFIREINSTRUCTIONRESCHEDULENEXTWITHEXISTINGCOUNT, the difference is that tasks that have already been MisFire will be ignored.
•MISFIRE_INSTRUCTION_SMART_POLICY All Trigger's MisFire default values are this, which roughly means "leave the processing logic to the smart Quartz to decide." The basic strategy is.
•If the schedule is only executed once, use MISFIRE_INSTRUCTION_FIRE_NOW.
•If it is an infinite scheduling (repeatCount is infinite), use MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT.
• Otherwise, using MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT MisFire is quite complicated, you can refer to this article.
Calendar
The Calendar here is not jdk's java.util.Calendar, not for calculating dates. Its function is to supplement the Trigger's time. Certain specific points in time can be excluded or added.
Taking "automatic repayment of card debts at 0:00 on the 25th of each month" as an example, we want to rule out the time point of February 25th of each year (because there is 2.14, February will definitely go bankrupt). This time can be achieved by Calendar.
example:
AnnualCalendar cal = new AnnualCalendar(); //Define a Calendar executed every year with a precision of days, that is, it cannot be defined until 2:00 pm on 2.25 java.util.Calendar excludeDay = new GregorianCalendar();excludeDay.setTime(newDate().inMonthOnDay(2, 25).build());cal.setDayExcluded(excludeDay, true); //Set exclusion date 2.25 scheduler.addCalendar("FebCal", cal, false, false); //Scheduler joins this Calendar//Define a TriggerTrigger trigger = newTrigger().withIdentity("trigger1", "group1") .startNow()//Once scheduler is added, it takes effect immediately.modifiedByCalendar("FebCal") //Use Calendar !! .withSchedule(simpleSchedule() .withIntervalInSeconds(1) .repeatForever()) .build();Quartz considerately provides us with the following Calendars. Note that all Calendars can be either excluding or inclusion depending on:
•HolidayCalendar. Specify a specific date, such as 20140613. Accuracy to the sky.
•DailyCalendar. Specifies the time period of each day (rangeStartingTime, rangeEndingTime), the format is HH:MM[:SS[:mmmm]]. That is, the maximum accuracy can reach milliseconds.
•WeeklyCalendar. Specify the day of the week of each week, the optional value is java.util.Calendar.SUNDAY. Accuracy is day.
•MonthlyCalendar. Specify the day of each month. Optional values are 1-31. Accuracy is day
•AnnualCalendar. Specify which day of the year. The usage method is as shown in the example above. Accuracy is day.
•CronCalendar. Specifies a Cron expression. The accuracy depends on the Cron expression, that is, the maximum accuracy can reach seconds.
Trigger implementation class
Quartz has the following Trigger implementations:
SimpleTrigger
Specifies tasks that start at a certain time and are performed at a certain time interval (in milliseconds). It is suitable for tasks similar to: start at 9:00, and execute every 1 hour. Its properties are:
•repeatInterval Repeat interval
•repeatCount Number of repetitions. The actual number of executions is repeatCount+1. Because it will be executed once at startTime. The same applies to the repeatCount property below.
example:
CalendarIntervalTrigger
Similar to SimpleTrigger, it specifies tasks that start at a certain time and are executed at a certain time interval. But the difference is that the time interval specified by SimpleTrigger is milliseconds, and there is no way to specify that it will be executed every other month (the monthly interval is not a fixed value), while the interval units supported by CalendarIntervalTrigger include seconds, minutes, hours, days, months, years, and weeks. Compared with SimpleTrigger, it has two advantages: 1. It is more convenient. For example, if you execute every 1 hour, you don’t have to calculate how many milliseconds 1 hour is equal to. 2. Supports intervals that are not fixed-length, such as intervals that are months and years. But the disadvantage is that the accuracy can only reach seconds. Its suitable task is similar to: start at 9:00 and execute once every week at 9:00. Its properties are:
•interval execution interval
•intervalUnit Units of execution interval (seconds, minutes, hours, days, months, years, weeks)
example:
calendarIntervalSchedule() .withIntervalInDays(1) //Execute once a day.build();calendarIntervalSchedule() .withIntervalInWeeks(1) //Execute once a week.build();
DailyTimeIntervalTrigger
Specify that tasks are performed at certain intervals during a certain period of time every day. And it can support specified weeks. Its suitable task is similar to: specifying 9:00 to 18:00 every day, executed every 70 seconds, and only Monday to Friday. Its properties are:
•startTimeOfDay Start time every day
•endTimeOfDay End Time of Day
•daysOfWeek The week to be executed
•interval execution interval
•intervalUnit Units of execution interval (seconds, minutes, hours, days, months, years, weeks)
•repeatCount Number of repetitions
example:
dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) // Start at 9:00 on the day.endingDailyAt(TimeOfDay.hourAndMinuteOfDay(16, 0)) // End at 16:00.onDaysOfTheWeek(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY) // Execute from Monday to Friday. withIntervalInHours(1) // Execute every 1 hour. withRepeatCount(100) //Repeat up to 100 times (actually executed 100+1 times) .build();dailyTimeIntervalSchedule() .startingDailyAt(TimeOfDay.hourAndMinuteOfDay(9, 0)) //Start at 9:00 on the day.endingDailyAfterCount(10) //Execute 10 times a day. This method actually calculates endTimeOfDay based on startTimeOfDay+interval*count .onDaysOfTheWeek(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY) //Execute from Monday to Friday.withIntervalInHours(1) //Execute every 1 hour interval.build();
CronTrigger
Suitable for more complex tasks, it supports syntax typed to Linux Cron (and is more powerful). Basically it covers most (but not all) of the above three Triggers - of course, it is also more difficult to understand. Its suitable tasks are similar to: each performed once every day at 0:00, 9:00, and 18:00. Its properties are only:
Cron expressions
But this representation itself is complex enough. There will be instructions below. example:
cronSchedule("0 0/2 8-17 * * ?") // Execute every 2 minutes at 8:00-17:00 every day.build();cronSchedule("0 30 9 ? * MON") // Execute every Monday at 9:30.build();weeklyOnDayAndHourAndMinute(MONDAY,9, 30) //Equivalent to 0 30 9 ? * MON .build();Cron expressions
| Location | Time domain | Allowed values | Special value |
| 1 | Second | 0-59 | , - * / |
| 2 | minute | 0-59 | , - * / |
| 3 | Hour | 0-23 | , - * / |
| 4 | date | 1-31 | , - * ? / LWC |
| 5 | month | 1-12 | , - * / |
| 6 | Week | 1-7 | , - * ? / LC # |
| 7 | Year (optional) | 1-31 | , - * / |
•Asterisk(): can be used in all fields to represent each moment in the corresponding time domain, for example, in the minute field, it means "per minute";
• Question mark (?): This character is only used in the Date and Week fields, and it is usually specified as a "meaningless value", equivalent to a dot character;
•Minus sign (-): Expresses a range. If "10-12" is used in the hour field, it means from 10 to 12 points, that is, 10,11,12;
•Comma(,): Expresses a list value. If "MON,WED,FRI" is used in the week field, it means Monday, Wednesday and Friday;
•Slash (/): x/y represents an equal-step sequence, x is the starting value and y is the incremental step value. If you use 0/15 in the minute field, it is expressed as 0, 15, 30 and 45 seconds, while 5/15 means 5, 20, 35, 50 in the minute field, you can also use */y, which is equivalent to 0/y;
•L: This character is only used in the Date and Week fields, representing the meaning of "Last", but it means differently in the two fields. L in the date field indicates the last day of the month, such as January 31, and February 28, which is not leap year; if L is used in the week, it indicates Saturday, which is equivalent to 7. However, if L appears in the week field and is preceded by a value X, it means "the last X days of the month", for example, 6L means the last Friday of the month;
•W: This character can only appear in the date field and is a modification of the leading date, indicating the working day closest to the date. For example, 15W represents the closest working day to the 15th of the month. If the 15th of the month is Saturday, it matches Friday the 14th; if the 15th of the month is Sunday, it matches Monday the 16th; if the 15th of the month is Tuesday, it is Tuesday the 15th. However, it must be noted that the matching date associated cannot be crossed to the month. If you specify 1W, if the 1st day is Saturday, the result matches Monday 3, not the last day of the last month. A W string can only specify a single date, but cannot specify a date range;
•LW combination: LW can be used in the date field, which means the last working day of the month; pound sign (#): This character can only be used in the week field, representing a working day of the month. For example, 6#3 represents the third Friday of the month (6 represents Friday, #3 represents the third one at the moment), while 4#5 represents the fifth Wednesday of the month, assuming that the month does not have the fifth Wednesday, it is ignored and not triggered;
•C: This character is only used in the Date and Week fields, representing the meaning of "Calendar". It means the date associated with the plan, and if the date is not associated, it is equivalent to all dates in the calendar. For example, 5C in the date field is equivalent to the first day after the 5th day of the calendar. 1C is equivalent to the first day after Sunday in the week field.
Cron expressions are not sensitive to the case of special characters and are not sensitive to the abbreviation of the week's English case. Some examples:
| Expression | illustrate |
| 0 0 12 * * ? | Run at 12 o'clock every day |
| 0 15 10 ? * * | Run at 10:15 every day |
| 0 15 10 * * ? | Run at 10:15 every day |
| 0 15 10 * * ? * | Run at 10:15 every day |
| 0 15 10 * * ? 2008 | Run at 10:15 a day in 2008 |
| 0 * 14 * * ? | Runs every minute between 14:00 and ends at 14:59 every day. |
| 0 0/5 14 * * ? | Run every 5 minutes from 14:00 to 15:00 every day, starting at 14:55 and ending at 14:55. |
| 0 0/5 14,18 * * ? | It runs every 5 minutes from 14:00 to 15:00 every day, and it runs every 5 minutes from 18:00 to 19:00 every day. |
| 0 0-5 14 * * ? | Run every minute from 14:00 to 14:05 every day. |
| 0 10,44 14 ? 3 WED | Run once a minute every Wednesday from 14:10 to 14:44. |
| 0 15 10 ? * MON-FRI | Run every Monday, Tuesday, Wednesday, Thursday, Thursday and Friday at 10:15. |
| 0 15 10 15 * ? | Run at 10:15 on the 15th of each month. |
| 0 15 10 L * ? | Run at 10:15 on the last day of each month. |
| 0 15 10 ? * 6L | Runs at 10:15 on the last Friday of each month. |
| 0 15 10 ? * 6L 2007-2009 | Run at 10:15 on the last Friday of each month in 2007, 2008, 2009. |
| 0 15 10 ? * 6#3 | Runs at 10:15 on the third Friday of each month. |
JobDetail & Job
JobDetail is the definition of a task, and Job is the execution logic of a task. A Job Class definition is referenced in the JobDetail. One simplest example:
public class JobTest { public static void main(String[] args) throws SchedulerException, IOException { JobDetail job=newJob() .ofType(DoNothingJob.class) //Check Job Class .withIdentity("job1", "group1") //Set name/group .withDescription("this is a test job") //Set description.usingJobData("age", 18) //Add attributes to ageJobDataMap .build(); job.getJobDataMap().put("name", "quertz"); //Add attribute name to JobDataMap //Define a SimpleTrigger trigger that executes once per second. Trigger trigger=newTrigger().startNow().withIdentity("trigger1") .withSchedule(simpleSchedule().withIntervalInSeconds(1) .repeatForever()) .build(); Scheduler sche=StdSchedulerFactory.getDefaultScheduler(); sche.scheduleJob(job, trigger); sche.start(); System.in.read(); sche.shutdown(); }}public class DoNothingJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("do nothing"); }}From the above example, we can see that to define a task, we need to do several things:
• Create an implementation class of org.quartz.Job and implement your own business logic. For example, the DoNothingJob above.
•Define a JobDetail, refer to this implementation class
• Join scheduleJob Quartz to schedule a task and will do the following:
•JobClass jobClass=JobDetail.getJobClass()
•Job jobInstance=jobClass.newInstance(). Therefore, the Job implementation class must have a public parameterless construction method.
•jobInstance.execute(JobExecutionContext context). JobExecutionContext is the context of the job running, and you can obtain information about Trigger, Scheduler, and JobDetail.
In other words, a new Job instance is created every time the schedule is that when some tasks are executed concurrently, there is no problem of accessing critical resources - of course, if you need to share a JobDataMap, there is still a problem of concurrent access to critical resources.
JobDataMap
Job is an instance of newInstance, so how do I pass the value to it? For example, I now have two tasks to send emails, one is to "liLei" and the other is to "hanmeimei". I can't say that I want to write two job implementation classes LiLeiSendEmailJob and HanMeiMeiSendEmailJob. The implementation method is through JobDataMap.
每一个JobDetail都会有一个JobDataMap。JobDataMap本质就是一个Map的扩展类,只是提供了一些更便捷的方法,比如getString()之类的。
我们可以在定义JobDetail,加入属性值,方式有二:
•newJob().usingJobData("age", 18) //加入属性到ageJobDataMap
•job.getJobDataMap().put("name", "quertz"); //加入属性name到JobDataMap
然后在Job中可以获取这个JobDataMap的值,方式同样有二:
public class HelloQuartz implements Job { private String name; public void execute(JobExecutionContext context) throws JobExecutionException { JobDetail detail = context.getJobDetail(); JobDataMap map = detail.getJobDataMap(); //方法一:获得JobDataMap System.out.println("say hello to " + name + "[" + map.getInt("age") + "]" + " at " + new Date()); } //方法二:属性的setter方法,会将JobDataMap的属性自动注入public void setName(String name) { this.name = name; }}对于同一个JobDetail实例,执行的多个Job实例,是共享同样的JobDataMap,也就是说,如果你在任务里修改了里面的值,会对其他Job实例(并发的或者后续的)造成影响。
除了JobDetail,Trigger同样有一个JobDataMap,共享范围是所有使用这个Trigger的Job实例。
Job并发
Job是有可能并发执行的,比如一个任务要执行10秒中,而调度算法是每秒中触发1次,那么就有可能多个任务被并发执行。
有时候我们并不想任务并发执行,比如这个任务要去”获得数据库中所有未发送邮件的名单“,如果是并发执行,就需要一个数据库锁去避免一个数据被多次处理。这个时候一个@DisallowConcurrentExecution解决这个问题。就是这样:
public class DoNothingJob implements Job { @DisallowConcurrentExecution public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("do nothing"); }}注意,@DisallowConcurrentExecution是对JobDetail实例生效,也就是如果你定义两个JobDetail,引用同一个Job类,是可以并发执行的。
JobExecutionException
Job.execute()方法是不允许抛出除JobExecutionException之外的所有异常的(包括RuntimeException),所以编码的时候,最好是try-catch住所有的Throwable,小心处理。
其他属性
•Durability(耐久性?) 如果一个任务不是durable,那么当没有Trigger关联它的时候,它就会被自动删除。
•RequestsRecovery 如果一个任务是"requests recovery",那么当任务运行过程非正常退出时(比如进程崩溃,机器断电,但不包括抛出异常这种情况),Quartz再次启动时,会重新运行一次这个任务实例。
可以通过JobExecutionContext.isRecovering()查询任务是否是被恢复的。
Scheduler
•Scheduler就是Quartz的大脑,所有任务都是由它来设施。
•Schduelr包含一个两个重要组件: JobStore和ThreadPool。
•JobStore是会来存储运行时信息的,包括Trigger,Schduler,JobDetail,业务锁等。它有多种实现RAMJob(内存实现),JobStoreTX(JDBC,事务由Quartz管理),JobStoreCMT(JDBC,使用容器事务),ClusteredJobStore(集群实现)、TerracottaJobStore(什么是Terractta)。
•ThreadPool就是线程池,Quartz有自己的线程池实现。所有任务的都会由线程池执行。
SchedulerFactory
SchdulerFactory,顾名思义就是来用创建Schduler了,有两个实现:DirectSchedulerFactory和StdSchdulerFactory。前者可以用来在代码里定制你自己的Schduler参数。后者是直接读取classpath下的quartz.properties(不存在就都使用默认值)配置来实例化Schduler。通常来讲,我们使用StdSchdulerFactory也就足够了。
SchdulerFactory本身是支持创建RMI stub的,可以用来管理远程的Scheduler,功能与本地一样,可以远程提交个Job什么的。DirectSchedulerFactory的创建接口:
/** * Same as * {@link DirectSchedulerFactory#createScheduler(ThreadPool threadPool, JobStore jobStore)}, * with the addition of specifying the scheduler name and instance ID. This * scheduler can only be retrieved via * {@link DirectSchedulerFactory#getScheduler(String)} * * @param schedulerName * The name for the scheduler. * @param schedulerInstanceId * The instance ID for the scheduler. * @param threadPool * The thread pool for executing jobs * @param jobStore * The type of job store * @throws SchedulerException * if initialization failed */ public void createScheduler(String schedulerName, String schedulerInstanceId, ThreadPool threadPool, JobStore jobStore) throws SchedulerException;StdSchdulerFactory的配置例子, 更多配置,参考Quartz配置指南:
org.quartz.scheduler.instanceName = DefaultQuartzSchedulerorg.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount = 10 org.quartz.threadPool.threadPriority = 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = trueorg.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
三、Quartz 集成Spring
开发一个job类,普通java类,需要有一个执行的方法:
package com.tgb.lk.demo.quartz;import java.util.Date;public class MyJob { public void work() { System.out.println("date:" + new Date().toString()); }}把类放到spring容器中,可以使用配置也可以使用注解:
<bean id="myJob" />
配置jobDetail,指定job对象:
<!-- 配置jobDetail,指定job对象--> <bean id="accountJobDetail"> <property name="targetObject"> <ref bean="accountJob" /> </property> <property name="targetMethod"> <value>work</value> </property> </bean>
配置一个trigger,需要指定一个cron表达式,指定任务的执行时机:
<!-- accountTrigger 的配置--> <bean id="accountTrigger" > <property name="jobDetail"> <ref bean="accountJobDetail" /> </property> <property name="cronExpression"> <value>0/3 * * * * ?</value> </property> </bean>
配置调度工厂:
<!-- 启动触发器的配置开始--> <bean name="startQuertz" lazy-init="false" autowire="no" > <property name="triggers"> <list> <ref bean="myJobTrigger" /> </list> </property> </bean> <!-- 启动触发器的配置结束-->
项目启动,定时器开始执行。
四、分析不同定时任务优缺点,寻找一种符合你项目需求的定时任务Timer管理延时任务的缺陷
以前在项目中也经常使用定时器,比如每隔一段时间清理项目中的一些垃圾文件,每隔一段时间进行日志清理;然而Timer是存在一些缺陷的,因为Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,会发生一些缺陷
Timer当任务抛出异常时的缺陷
如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行
Timer执行周期任务时依赖系统时间
Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化会出现一些执行上的变化,ScheduledExecutorService基于时间的延迟,不会由于系统时间的改变发生执行变化。
对异常的处理
Quartz的某次执行任务过程中抛出异常,不影响下一次任务的执行,当下一次执行时间到来时,定时器会再次执行任务;而TimerTask则不同,一旦某个任务在执行过程中抛出异常,则整个定时器生命周期就结束,以后永远不会再执行定时器任务。
精确到和功能
Quartz每次执行任务都创建一个新的任务类对象,而TimerTask则每次使用同一个任务类对象。 Quartz可以通过cron表达式精确到特定时间执行,而TimerTask不能。Quartz拥有TimerTask所有的功能,而TimerTask则没有上述,基本说明了在以后的开发中尽可能使用ScheduledExecutorService(JDK1.5以后)替代Timer。
五、cron 在线表达式生成器http://cron.qqe2.com/附录cron 表达式
cron表达式用于配置cronTrigger的实例。cron表达式实际上是由七个子表达式组成。这些表达式之间用空格分隔。
•Seconds (秒)
•Minutes(分)
•Hours(小时)
•Day-of-Month (天)
•Month(月)
•Day-of-Week (周)
•Year(年)
例:"0 0 12 ? * WED” 意思是:每个星期三的中午12点执行。个别子表达式可以包含范围或者列表。例如:上面例子中的WED可以换成"MON-FRI","MON,WED,FRI",甚至"MON-WED,SAT"。子表达式范围:
•Seconds (0~59)
•Minutes (0~59)
•Hours (0~23)
•Day-of-Month (1~31,但是要注意有些月份没有31天)
•Month (0~11,或者"JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV,DEC")
•Day-of-Week (1~7,1=SUN 或者"SUN, MON, TUE, WED, THU, FRI, SAT”)
•Year (1970~2099)
Cron表达式的格式:秒分时日月周年(可选)。
字段名| 允许的值| 允许的特殊字符------- | ------ | ------ | ------ 秒| 0-59 | , - * / 分| 0-59 | , - * / 小时| 0-23 | , - * / 日| 1-31 | , - * ? / LWC 月| 1-12 or JAN-DEC | , - * / 周几| 1-7 or SUN-SAT | , - * ? / LC # 年(可选字段) | empty 1970-2099 | , - * /
字符含义:
•*:代表所有可能的值。因此,“*”在Month中表示每个月,在Day-of-Month中表示每天,在Hours表示每小时
•-:表示指定范围。
•,:表示列出枚举值。例如:在Minutes子表达式中,“5,20”表示在5分钟和20分钟触发。
•/:被用于指定增量。例如:在Minutes子表达式中,“0/15”表示从0分钟开始,每15分钟执行一次。"3/20"表示从第三分钟开始,每20分钟执行一次。和"3,23,43"(表示第3,23,43分钟触发)的含义一样。
•?:用在Day-of-Month和Day-of-Week中,指“没有具体的值”。当两个子表达式其中一个被指定了值以后,为了避免冲突,需要将另外一个的值设为“?”。例如:想在每月20日触发调度,不管20号是星期几,只能用如下写法:0 0 0 20 * ?,其中最后以为只能用“?”,而不能用“*”。
•L:用在day-of-month和day-of-week字串中。它是单词“last”的缩写。它在两个子表达式中的含义是不同的。
•在day-of-month中,“L”表示一个月的最后一天,一月31号,3月30号。
•在day-of-week中,“L”表示一个星期的最后一天,也就是“7”或者“SAT”
•如果“L”前有具体内容,它就有其他的含义了。例如:“6L”表示这个月的倒数第六天。“FRIL”表示这个月的最后一个星期五。
•注意:在使用“L”参数时,不要指定列表或者范围,这样会出现问题。
•W:“Weekday”的缩写。只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在day-of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日,即最后一个星期五。
•# :只能用在day-of-week字段。用来指定这个月的第几个周几。例:在day-of-week字段用"6#3" or "FRI#3"指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。
表达式例子:
0 * * * * ? 每1分钟触发一次
0 0 * * * ? 每天每1小时触发一次
0 0 10 * * ? 每天10点触发一次
0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
0 30 9 1 * ? 每月1号上午9点半
0 15 10 15 * ? 每月15日上午10:15触发
*/5 * * * * ? 每隔5秒执行一次
0 */1 * * * ? 每隔1分钟执行一次
0 0 5-15 * * ? 每天5-15点整点触发
0 0/3 * * * ? 每三分钟触发一次
0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0 12 ? * WED 表示每个星期三中午12点
0 0 17 ? * TUES,THUR,SAT 每周二、四、六下午五点
0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
0 0 23 L * ? 每月最后一天23点执行一次
0 15 10 L * ? 每月最后一日的上午10:15触发
0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
0 15 10 * * ? 2005 2005年的每天上午10:15触发
0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
以上这篇Java实现Web应用中的定时任务(实例讲解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持武林网。