In almost all projects, the use of scheduled tasks is indispensable and even costly if not used properly. I still remember many years ago when I was working in the financial system, the payment business was made through the scheduled task. At that time, due to the limited processing capacity of the bank interface and the improper use of the scheduled task, a large number of repeated payment requests were issued. Fortunately, after the link will trade card in the system internal, did not happen capital loss.

Therefore, it is necessary to systematically learn the scheduled task. This article gives you an overview of several common scheduled task implementations in the Java domain.

Thread waiting implementation

Let’s start with the original and simplest way. You can create a thread and let it run in the while loop, using the sleep method to schedule tasks.

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(); }}Copy the code

This approach is simple and straightforward, but it has limited functionality and needs to be implemented by yourself.

The JDK comes with a Timer implementation

By far the oldest implementation of scheduled tasks is the Timer API in the JDK. Timer is a Timer tool used to schedule the execution of a given task in a background thread. It can schedule tasks to be “executed once” or “executed multiple times” on a regular basis.

In actual development, it is often necessary to perform some periodic operations, such as performing an operation every 5 minutes. The most convenient and efficient way to do this is to use the java.util.Timer utility class.

Core method

The core method of the Timer class is as follows:

// Execute the specified task after the specified delay time schedule(TimerTask task,long delay); // Execute the specified task at the specified time. Schedule (TimerTask task, Date time); // After the specified delay, the specified task is repeatedly executed at the specified interval. Schedule (TimerTask task,long delay,long period); // Start the specified task at the specified time and execute the specified task at the specified interval (period) schedule(TimerTask task, Date firstTime, long period); ScheduleAtFixedRate (TimerTask task,Date firstTime, Long Period); ScheduleAtFixedRate (TimerTask task,long delay,long period); // Terminate the timer, discarding all currently scheduled tasks. Cancal (); // Remove all cancelled tasks from this timer's task queue. Purge ();Copy the code

Use the sample

Here are a few examples to illustrate the use of the core methods. Start by defining a generic TimerTask class that defines tasks to execute with.

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 +") ); }}Copy the code

Specify a delay

This is a common scenario. For example, after the system initializes a component, the execution of a scheduled task is delayed for a few seconds.

public class DelayOneDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new DoSomethingTimerTask("DelayOneDemo"),1000L); }}Copy the code

Execute the above code, execute the scheduled task after a delay of one second, and print the result. The second parameter is in milliseconds.

Fixed interval execution

A scheduled task starts to be executed at a specified delay and is executed at a fixed interval. For example, the execution delay is 2 seconds and the fixed execution interval is 1 second.

public class PeriodDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new DoSomethingTimerTask("PeriodDemo"),2000L,1000L); }}Copy the code

When you execute the program, you will find that it starts after 2 seconds and executes at 1 second intervals.

Fixed rate execution

A scheduled task starts to be executed at the specified delay time and is executed at a fixed rate. For example, the delay is 2 seconds and the fixed rate is 1 second.

public class FixedRateDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.scheduleAtFixedRate(new DoSomethingTimerTask("FixedRateDemo"),2000L,1000L); }}Copy the code

When you execute the program, you will find that it starts after 2 seconds and executes at 1 second intervals.

Schedule and scheduleAtFixedRate have the same effect. Why are they provided? What’s the difference between them?

Schedule is different from scheduleAtFixedRate

Before looking at the differences between the schedule and scheduleAtFixedRate methods, let’s look at the similarities:

  • If the task execution does not time out, next execution time = start time of last execution + period;
  • Task execution times out. Next execution time = Last execution end time.

They are the last execution time plus the interval to execute the next task when the task execution has not timed out. When the execution times out, it is executed immediately.

The scheduleAtFixedRate method is more focused on keeping the interval time stable, while the scheduleAtFixedRate method is more focused on keeping the frequency of execution stable.

Schedule focuses on keeping the interval time stable

The schedule method delays the scheduled task due to the delay of the previous task. The formula is scheduledExecutionTime(the NTH + 1st time) = realExecutionTime(the NTH time) + periodTime.

If systemCurrentTime>= scheduledExecutionTime(the NTH + 1st time) after the task is executed, the NTH + 1st time task is executed immediately.

The scheduledExecutionTime of the NTH + 2nd task becomes realExecutionTime(the NTH + 1st time)+periodTime. This method focuses more on keeping the interval time stable.

ScheduleAtFixedRate keeps the execution frequency stable

ScheduleAtFixedRate scheduleAtFixedRate scheduleAtFixedRate scheduleAtFixedRate scheduleAtFixedRate scheduleAtFixedRate scheduleAtFixedRate scheduleAtFixedRate scheduleAtFixedRate ScheduledExecutionTime (NTH time)=firstExecuteTime +n*periodTime.

If it takes too long to execute a task for the NTH time and the systemCurrentTime>= scheduledExecutionTime(the NTH + 1st time) after the task is executed, the NTH + 1st time is executed immediately.

The scheduledExecutionTime of the following task the NTH + 2nd time is still firstExecuteTime+ (n+2)*periodTime. This is fixed the first time the task was executed. To put it bluntly, this approach focuses more on keeping the frequency of execution stable.

The difference between schedule and scheduleAtFixedRate is as follows: The policy of schedule is that if a task is missed, it will be missed, and the schedule will follow the new rhythm. ScheduleAtFixedRate’s strategy is to try to catch up if you miss it.

The defect of the Timer

Timer The Timer can be timed (the task is executed at a specified time), delayed (the task is executed five seconds later), or periodically (the task is executed every one second). However, Timer has some drawbacks. First, the Timer’s support for scheduling is based on absolute time, not relative time, so it is very sensitive to changes in system time.

Second, the Timer thread will not catch exceptions. If the TimerTask throws an unchecked exception, the Timer thread will terminate. Meanwhile, the Timer will not resume the execution of the thread, it will mistakenly think that the entire Timer thread will cancel. At the same time, scheduled timerTasks that have not yet been executed will no longer be executed, and new tasks will not be scheduled. So if TimerTask throws an unchecked exception, the Timer will generate unexpected behavior.

The JDK bring ScheduledExecutorService

ScheduledExecutorService is a new interface created after JAVA 1.5. ScheduledExecutorService is a scheduled task class based on the thread pool. Each scheduling task is assigned to a thread in the thread pool to execute. In other words, tasks are executed concurrently and do not affect each other.

Note that ScheduledExecutorService starts a thread only when a scheduling task is executed. The rest of the time ScheduledExecutorService is in the polling state.

There are four methods of ScheduledExecutorService:

ScheduledFuture<? > schedule(Runnable command,long delay, TimeUnit unit); <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit); ScheduledFuture<? > scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit); ScheduledFuture<? > scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);Copy the code

ScheduleAtFixedRate and scheduleWithFixedDelay in the implementation of the timing program is more convenient, the use of more.

The ScheduledExecutorService interface methods are almost the same as those in the Timer, except that the scheduled method of the Timer requires an abstract task of the TimerTask to be passed in. ScheduledExecutorService encapsulation is more detailed, pass Runnable or Callable internal will do a layer of encapsulation, encapsulation of a TimerTask (ScheduledFutureTask). The thread pool is then passed in to start the thread to perform the task.

ScheduleAtFixedRate methods

The scheduleAtFixedRate method is used to execute a task at a specified frequency. Definition and parameter description:

public ScheduledFuture<? > scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);Copy the code

The meanings of parameters are as follows: command indicates the thread to be executed. InitialDelay indicates the delayed execution time after initialization. “Period” is the minimum interval between two starts. Unit is the unit of time.

Example:

public class ScheduleAtFixedRateDemo implements Runnable{ public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleAtFixedRate( new ScheduleAtFixedRateDemo(), 0, 1000, TimeUnit.MILLISECONDS); } @override public void run() {system.out.println (new Date() + ": ScheduleAtFixedRateDemo is being executed." ); try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

This is the basic use of the scheduleAtFixedRate method, but when the program is executed it is not executed at 1 second intervals, but at 2 seconds.

“ScheduleAtFixedRate” is used to execute tasks at an interval of “period”. If “period” is less than “period”, “period” is used to execute the next task after the last one is completed. However, if the execution time of the task is longer than period, the next task starts immediately after the last one is completed.

ScheduleWithFixedDelay method

The scheduleWithFixedDelay method to execute a task at a specified frequency interval. Definition and parameter description:

public ScheduledFuture<? > scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);Copy the code

The meanings of parameters are as follows: command indicates the thread to be executed. InitialDelay indicates the delayed execution time after initialization. “Period” is the interval between the end of the previous execution and the start of the next execution. Unit is the unit of time.

Example:

public class ScheduleAtFixedRateDemo implements Runnable{ public static void main(String[] args) { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); executor.scheduleWithFixedDelay( new ScheduleAtFixedRateDemo(), 0, 1000, TimeUnit.MILLISECONDS); } @override public void run() {system.out.println (new Date() + ": ScheduleAtFixedRateDemo is being executed." ); try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

The above is the basic use of the scheduleWithFixedDelay method, but when you execute the program you will find that it does not execute at 1 second interval, but at 3 seconds interval.

This is because scheduleWithFixedDelay means that no matter how long the task has been executed, it will wait until the last task has completed before delaying the next task.

Quartz framework implementation

In addition to the JDK’s own APIS, we can use open source frameworks such as Quartz.

Quartz is an open source project in the field of Job scheduling. Quartz can be used either alone or in conjunction with the Spring framework, which is usually used in actual development. Using Quartz, you can develop one or more scheduled tasks, and each scheduled task can be executed at a specific time, such as every hour, 10 am on the first day of the month, 5 PM on the last day of the month, etc.

Quartz is typically made up of three parts: a Scheduler, a task (JobDetail), and triggers (Triggers, including SimpleTrigger and CronTrigger). The following is illustrated with specific examples.

Quartz integration

To use Quartz, you first need to introduce the dependencies in your project’s POM file:

< the dependency > < groupId > org. Quartz - the scheduler < / groupId > < artifactId > quartz < / artifactId > < version > 2.3.2 < / version > </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> < version > < / version 2.3.2 > < / dependency >Copy the code

Define the Job to perform the task. Here we implement the Job interface provided by Quartz:

public class PrintJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {system.out.println (new Date() + ": task 'PrintJob' was executed." ); }}Copy the code

Create Scheduler and Trigger and execute scheduled tasks:

Public class MyScheduler {public static void main(String[] args) throws SchedulerException {// 1 SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); // 2, create JobDetail instance, JobDetail JobDetail = JobBuilder. NewJob (printJob.class).withidentity (" Job ", "group").build(); // 3, create Trigger instance, Every 1 s to perform a Trigger the Trigger. = TriggerBuilder newTrigger () withIdentity (" Trigger ", "TriggerGroup). StartNow () / / take effect immediately. The withSchedule (SimpleScheduleBuilder. SimpleSchedule () .repeatForever()).build();.repeatforever ().build();.repeatforever ().build(); //4, Scheduler binds to Job and Trigger, and executes Scheduler. ScheduleJob (jobDetail, Trigger); System.out.println("--------scheduler start ! -- -- -- -- -- -- -- -- -- -- -- -- "); scheduler.start(); }}Copy the code

Execute the program, you can see every 1 second to execute a scheduled task.

In the above code, where Job is the Interface of Quartz, the implementation of the business logic is implemented by implementing this interface.

JobDetail is bound to a specified Job. Each time Scheduler schedules a Job, it obtains the corresponding Job first, creates the Job instance, and executes the execute() content of the Job. After the task is executed, the associated Job object instance is released. And will be cleared by the JVM GC.

The Trigger is a Quartz Trigger that tells the Scheduler when to execute the Job. SimpleTrigger can execute a job task within a specified time period or execute a job task multiple times within a time period.

CronTrigger is a powerful, calendar-based job scheduler, whereas SimpleTrigger is precisely spaced, so CroTrigger is more commonly used than SimpleTrigger. CroTrigger is based on Cron expressions.

The following is an example of a common Cron expression:

As you can see, the Quartz based CronTrigger enables a very rich set of scheduled task scenarios.

Spring Task

Starting with Spring 3, Spring comes with a set of timing tools, Spring-Task, which can be thought of as a lightweight Quartz that is easy to use, requires no packages other than those associated with Spring, and supports both annotations and configuration files. In general, within the Spring architecture, for simple scheduled tasks, you can directly use the functions provided by Spring.

Let’s skip the XML-based configuration file format and look directly at the annotation-based implementation. It’s very simple to use, just go to code:

@Component("taskJob") public class TaskJob { @Scheduled(cron = "0 0 3 * * ?" ) public void job1() {system.out.println (" scheduled tasks defined by cron "); } @scheduled (fixedDelay = 1000L) public void job2() {system.out.println (" Scheduled task defined by fixedDelay ");} @scheduled (fixedDelay = 1000L) public void job2() {system.out.println (" Scheduled task defined by fixedDelay "); } @scheduled (fixedRate = 1000L) public void job3() {system.out.println (" fixed task defined by fixedRate "); }}Copy the code

If you are in a Spring Boot project, you need to add @enablescheduling to the Boot class to enable the scheduled task.

In the above code, @Component is used to instantiate the class, which is independent of the scheduled task. @Scheduled specifies that the method is executed based on Scheduled tasks, and that the frequency of execution is determined by the expression specified by Cron. The expression used by CronTrigger above the cron expression is the same. In contrast to Cron, Spring also provides fixedDelay and fixedRate forms of scheduled task execution.

The difference between fixedDelay and fixedRate

The difference between fixedDelay and fixedRate is very similar to the difference in Timer.

FixedRate has a schedule concept. When a task is started, T1, T2, and T3 are already scheduled for execution, such as 1 minute, 2 minutes, and 3 minutes. When T1 is executed for more than 1 minute, T2 is delayed, and T2 is executed immediately when T1 is finished.

FixedDelay is simple. It indicates the interval between the end of the last task and the start of the next task. The interval between the two tasks is always the same regardless of how long it takes to execute.

Disadvantages of Spring Tasks

Spring Task itself does not support persistence, and there is no official distributed cluster mode. It can only be manually extended by developers in business applications, which cannot meet the requirements of visualization and easy configuration.

Distributed task scheduling

The above scheduled task scenarios are single-machine and can only be used in a single JVM process. However, the distributed task scheduling framework with high performance, high availability and scalability is needed in the distributed environment.

Quartz is distributed

First, Quartz can be used in distributed scenarios, but needs to be in the form of database locks. In simple terms, Quartz’s distributed scheduling strategy is an asynchronous strategy with database as the boundary. Each scheduler follows an operation rule based on database locks to ensure that the operation is unique, while the asynchronous operation of multiple nodes ensures that the service is reliable.

Quartz distributed scheme solves the task only, therefore, a high availability (reduce) a single point of failure problem, processing capacity bottlenecks in the database, and there is no level of tasks shard, unable to maximize efficiency, can only rely on shedulex do shard scheduling level, but do parallel scheduling layer subdivision is hard to run in combination with the actual resources do optimal shard.

Lightweight artifact XXL-Job

Xxl-job is a lightweight distributed task scheduling platform. Features are platform, easy deployment, rapid development, simple learning, lightweight, easy to expand. Scheduled tasks are executed by the scheduling center and the executor. The scheduling center is responsible for unified scheduling, and the actuator is responsible for receiving and executing the scheduling.

For small and medium-sized projects, this framework is used more.

Other frameworks

In addition, there are elastical-job, Saturn, sia-task and so on.

Elastic-Job is a distributed scheduling solution with highly available features.

Saturn is vipshop’s open source distributed task scheduling platform, which is based on Elastic Job.

Sia-task is creditEase’s open source distributed TASK scheduling platform.

summary

Through this paper summarizes the implementation of six kinds of scheduled tasks, in terms of the application of practical scenarios, most of the current system has been out of the single-machine mode. Xxl-job may be a good choice for systems where the amount of concurrency is not too high.

Source address: github.com/secbr/java-…

Author of SpringBoot Tech Insider, loves to delve into technology and write about it.

Official account: “Program New Horizon”, the official account of the blogger, welcome to follow ~

Technical exchange: please contact the blogger wechat id: Zhuan2quan