How many of the six implementations of timed tasks in Java do you know?

Posted by makaveli80 on Tue, 04 Jan 2022 14:22:10 +0100

In almost all projects, the use of scheduled tasks is indispensable. If used improperly, it will even cause asset damage. I still remember that many years ago, when I was working in the financial system, the disbursement business was to make payments through scheduled tasks. At that time, due to the limited processing capacity of the bank interface and the improper use of scheduled tasks, a large number of repeated disbursement requests were issued. Fortunately, the transaction card was placed inside the system in the later link, and no asset loss occurred.

Therefore, it is very necessary to systematically learn about timed tasks. This article will take you through several common timed task implementations in the Java field.

Thread waiting for implementation

First from the most primitive and simplest way to explain. You can create a thread first, and then let it run all the time in the while loop. You can achieve the effect of scheduled tasks through the sleep method.

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

This method is simple and direct, but the functions that can be realized are limited, and they need to be realized by themselves.

JDK comes with Timer implementation

At present, the Timer API provided by JDK is the oldest way to implement timed tasks. Timer is a timer tool used to schedule the execution of specified tasks in a background thread. It can schedule tasks to "execute once" or "execute multiple times" on a regular basis.

In the actual development, some periodic operations are often required, such as executing an operation every 5 minutes. The most convenient and efficient way to implement this operation is to use Java util. Timer utility class.

Core method

The core methods of Timer class are as follows:

// Executes the specified task after the specified delay time
schedule(TimerTask task,long delay);

// Execute the specified task at the specified time. (execute only once)
schedule(TimerTask task, Date time);

// After delaying the specified time (delay), start to repeat the specified task at the specified interval (period)
schedule(TimerTask task,long delay,long period);

// Start at the specified time and repeat the specified task at the specified interval
schedule(TimerTask task, Date firstTime , long period);

// Start repeating the fixed rate execution task at the specified time
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);

// Start repeating the fixed rate execution task after the specified delay
scheduleAtFixedRate(TimerTask task,long delay,long period);

// Terminate this timer and discard all currently scheduled tasks.
cancal();

// Remove all cancelled tasks from the task queue of this timer.
purge();

Use example

Here are a few examples to demonstrate the use of the core method. First, define a general TimerTask class to define the tasks to be executed 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() + " : task「" + taskName + "」Executed.");
    }
}

Specifies that execution is delayed once

Execute once after the specified delay time. This is a common scenario. For example, after the system initializes a component, delay it for a few seconds, and then execute the scheduled task.

public class DelayOneDemo {

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

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

Fixed interval execution

The scheduled task starts to execute at the specified delay time, and the scheduled task is executed at a fixed interval. For example, the execution is delayed by 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);
    }
}

When the program is executed, it will be found that it will be executed every 1 second after 2 seconds.

Fixed rate execution

The scheduled task starts to execute at the specified delay time, and the scheduled task executes at a fixed rate. For example, the execution is delayed for 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);
    }
}

When the program is executed, it will be found that it will be executed every 1 second after 2 seconds.

At this time, do you wonder why schedule and scheduleAtFixedRate have the same effect, why they provide two methods, and what are the differences between them?

The difference between schedule and scheduleAtFixedRate

Before understanding the differences between schedule and scheduleAtFixedRate methods, look at their similarities:

  • Task execution did not time out. Next execution time = last execution start time + period;
  • Task execution timeout, next execution time = last execution end time;

When the task execution is not timed out, they are the last execution time plus the interval time to execute the next task. When the execution exceeds the time limit, it is executed immediately.

The difference lies in the different emphasis. The schedule method focuses on maintaining the stability of interval time, while the scheduleAtFixedRate method focuses more on maintaining the stability of execution frequency.

schedule focuses on keeping the interval stable

The schedule method will delay the following scheduled tasks due to the delay of the previous task. The calculation formula is scheduledExecutionTime (n + 1st time) = realexecutiontime (n time) + periodTime.

In other words, if the execution time of the nth task is too long for some reason, and the systemcurrenttime > = scheduled execution time (n+1 time) after execution is completed, the n+1 task will be executed immediately without interval waiting.

In the next N + 2nd time, the scheduled execution time (n + 2nd time) of the task becomes real execution time (n + 1st time) + periodTime. This method pays more attention to maintaining the stability of interval time.

scheduleAtFixedRate keeps the execution frequency stable

When scheduleAtFixedRate repeatedly executes the plan of a task, the scheduled execution time of each task is initially determined, that is, scheduledExecutionTime (nth time) = firstExecuteTime +n*periodTime.

If the execution time of the nth task is too long for some reason, and the systemcurrenttime > = scheduledExecutionTime (n + 1st time) after execution is completed, the period interval will not be waited at this time, and the nth + 1st task will be executed immediately.

The scheduled execution time (n+2) of the next n+2 task is still the firstExecuteTime + (n+2)*periodTime, which is determined when the task is executed for the first time. To put it bluntly, this method pays more attention to maintaining the stability of execution frequency.

If you use one sentence to describe the difference between schedule and scheduleAtFixedRate after task execution timeout, the strategy of schedule is to miss if you miss, and follow the new rhythm; The strategy of scheduleAtFixedRate is to try to catch up with the original rhythm (established rhythm) if you miss it.

Defects of Timer

Timer timer can execute tasks regularly (at a specified time), late (5 seconds later) and periodically (every 1 second). However, timer has some defects. Firstly, timer's support for scheduling is based on absolute time rather than relative time, so it is very sensitive to the change of system time.

Secondly, the Timer thread will not catch exceptions. If an unchecked exception is thrown by the TimerTask, the Timer thread will terminate. At the same time, the Timer will not resume the execution of the thread. It will mistakenly think that the whole Timer thread will be cancelled. At the same time, TimerTask that has been scheduled but not yet executed will not be executed, and new tasks cannot be scheduled. Therefore, if TimerTask throws an unchecked exception, Timer will produce unpredictable behavior.

JDK comes with ScheduledExecutorService

ScheduledExecutorService is a new scheduled task interface after JAVA 1.5. It is a scheduled task class designed based on thread pool. Each scheduled task will be assigned to a thread in the thread pool for execution. In other words, tasks are executed concurrently and do not affect each other.

It should be noted that the ScheduledExecutorService will actually start a thread only when the scheduled task is executed, and the ScheduledExecutorService is in the polling task state for the rest of the time.

ScheduledExecutorService mainly has the following four methods:

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);

Among them, scheduleAtFixedRate and scheduleWithFixedDelay are more convenient and used in the implementation of timing programs.

The four interface methods defined in ScheduledExecutorService are almost the same as the corresponding methods in Timer, except that the scheduled method of Timer needs to pass in an abstract task of TimerTask externally.
The ScheduledExecutorService is encapsulated in more detail. A layer of encapsulation will be made inside Runnable or Callable to encapsulate an abstract task class (ScheduledFutureTask) similar to TimerTask. Then, it will be passed into the thread pool and start the thread to execute the task.

scheduleAtFixedRate method

The scheduleAtFixedRate method executes a task at a specified frequency and cycle. Definition and Parameter Description:

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

Parameter corresponding meaning: command refers to the thread to be executed; initialDelay is the delayed execution time after initialization; period is the minimum interval between two execution starts; Unit is the timing unit.

Usage 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() + " : task「ScheduleAtFixedRateDemo」Executed.");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The above is the basic usage of the scheduleAtFixedRate method, but when executing the program, you will find that it is not executed every 1 second, but every 2 seconds.

This is because scheduleAtFixedRate executes tasks at intervals of period. If the task execution time is less than period, the next task will be executed after the interval of period after the last task is completed; However, if the task execution time is greater than period, the next task will be started immediately after the last task is executed.

scheduleWithFixedDelay method

The scheduleWithFixedDelay method executes a task at a specified frequency interval. Definition and Parameter Description:

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

Parameter corresponding meaning: command refers to the thread to be executed; initialDelay is the delayed execution time after initialization; period is the interval from the end of the previous execution to the beginning of the next execution (interval execution delay time); unit is the timing unit.

Usage 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() + " : task「ScheduleAtFixedRateDemo」Executed.");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The above is the basic usage of the scheduleWithFixedDelay method, but when executing the program, you will find that it is not executed every 1 second, but every 3 seconds.

This is because no matter how long the task is executed, the scheduleWithFixedDelay will wait until the last task is executed, and then delay to execute the next task.

Quartz framework implementation

In addition to the JDK's own API, we can also use an open source framework, such as Quartz.

Quartz is Job scheduling An open source project in the field of (Job scheduling). Quartz can be used alone or integrated with the spring framework. The latter is generally used in actual development. Quartz can be used to develop one or more scheduled tasks. Each scheduled task can be executed separately, such as every 1 hour, at 10 a.m. on the first day of each month, and every day At 5:00 p.m. on the last day of the month.

Quartz is usually composed of three parts: Scheduler, JobDetail and Trigger, including SimpleTrigger and CronTrigger. The following is a specific example.

Quartz integration

To use Quartz, you first need to introduce the corresponding dependencies in the project's pom file:

<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>

Define a Job to execute a task. Here, you need to 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」Executed.");
    }
}

Create schedulers and triggers and perform scheduled tasks:

public class MyScheduler {

    public static void main(String[] args) throws SchedulerException {
        // 1. Create Scheduler
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        // 2. Create a JobDetail instance and bind it to the PrintJob class (Job execution content)
        JobDetail jobDetail = JobBuilder.newJob(PrintJob.class)
                .withIdentity("job", "group").build();
        // 3. Build Trigger instance and execute it every 1s
        Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup")
                .startNow()//Effective immediately
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(1)//Every 1s
                        .repeatForever()).build();//Always execute

        //4. Scheduler binds Job and Trigger and executes
        scheduler.scheduleJob(jobDetail, trigger);
        System.out.println("--------scheduler start ! ------------");
        scheduler.start();
    }
}

Execute the program, and you can see that the scheduled task is executed every 1 second.

In the above code, Job is the interface of Quartz, and the implementation of business logic is realized by implementing this interface.

JobDetail binds the specified Job. Each time the Scheduler schedules the execution of a Job, it will first get the corresponding Job, then create the Job instance, and then execute the execute() content in the Job. After the task is executed, the associated Job object instance will be released and cleared by the JVM GC.

Trigger is a trigger of Quartz, which is used to inform the Scheduler when to execute the corresponding Job. SimpleTrigger can execute a Job task once in a specified time period or multiple times in a time period.

CronTrigger has very powerful functions. It is a calendar based job scheduling, while SimpleTrigger precisely specifies the interval, so CroTrigger is more commonly used than SimpleTrigger. CroTrigger is based on Cron expression.

Examples of common Cron expressions are as follows:

It can be seen that CronTrigger based on Quartz can realize very rich timed task scenarios.

Spring Task

Starting from Spring 3, Spring comes with a set of timed task tool Spring task, which can be regarded as a lightweight Quartz. It is very simple to use. In addition to Spring related packages, there is no need for additional packages. It supports annotation and configuration files. Generally, in the Spring system, you can directly use the functions provided by Spring for simple scheduled tasks.

The form based on XML configuration file is no longer introduced. Let's look directly at the implementation based on annotation. It is very simple to use. You can directly use the code:

@Component("taskJob")
public class TaskJob {

    @Scheduled(cron = "0 0 3 * * ?")
    public void job1() {
        System.out.println("adopt cron Defined scheduled tasks");
    }

    @Scheduled(fixedDelay = 1000L)
    public void job2() {
        System.out.println("adopt fixedDelay Defined scheduled tasks");
    }

    @Scheduled(fixedRate = 1000L)
    public void job3() {
        System.out.println("adopt fixedRate Defined scheduled tasks");
    }
}

If you are in the Spring Boot project, you need to add @ enableshcheduling on the startup class to start 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 the specific execution frequency is determined by the expression specified by cron. The cron expression is consistent with the expression used by CronTrigger above. In contrast to cron, Spring also provides two forms of timed task execution: fixedDelay and fixedRate.

Difference between fixedDelay and fixedRate

The differences between fixedDelay and fixedRate are similar to those in Timer.

fixedRate has a timetable concept. When a task is started, T1, T2 and T3 have scheduled the execution time, such as 1 minute, 2 minutes and 3 minutes. When the execution time of T1 is greater than 1 minute, T2 will be delayed. When T1 is completed, T2 will execute immediately.

fixedDelay is relatively simple, indicating the time interval from the end of the previous task to the beginning of the next task. No matter how long the task takes to execute, the interval between the two tasks is always the same.

Disadvantages of Spring Task

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

Distributed task scheduling

The above scheduled task schemes are for single machines and can only be used in a single JVM process. Now it is basically a distributed scenario, which requires a set of distributed task scheduling framework with high performance, high availability and scalability in a distributed environment.

Quartz distributed

First, quartz can be used in distributed scenarios, but it needs to be in the form of database lock. In short, the distributed scheduling strategy of quartz is an asynchronous strategy with database as the boundary. Each scheduler abides by an operation rule based on database lock, which ensures the uniqueness of the operation. At the same time, the asynchronous operation of multiple nodes ensures the reliability of the service.

Therefore, Quartz's distributed solution only solves the problem of high availability of tasks (reducing single point of failure). The bottleneck of processing capacity lies in the database, and there is no task fragmentation at the execution level, which can not maximize efficiency. It can only rely on the shedulex scheduling level for fragmentation, but the scheduling layer for parallel fragmentation is difficult to do the optimal fragmentation in combination with the actual operation resources.

Lightweight artifact XXL job

XXL-JOB is a lightweight distributed task scheduling platform. It is characterized by platform, easy deployment, rapid development, simple learning, lightweight and easy expansion. The scheduling center and actuator function complete the execution of scheduled tasks. The dispatching center is responsible for unified dispatching, and the actuator is responsible for receiving and executing the dispatching.

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

Other frameworks

In addition, there are elastic job, Saturn, SIA-TASK, etc.

Elastic job is a distributed scheduling solution with high availability.

Saturn is an open source distributed task scheduling platform of vipshop, which has been transformed on the basis of Elastic Job.

SIA-TASK is an open source distributed task scheduling platform of Yixin.

Summary

This paper combs the realization of six timing tasks. In terms of the application of practical scenarios, most systems have separated from the single machine mode. For systems where concurrency is not too high, XXL job may be a good choice.

Source address: https://github.com/secbr/java-schedule

About the blogger: the author of the technical book "inside of SpringBoot technology", loves to study technology and write technical dry goods articles.

The official account: "new horizon of procedures", the official account of bloggers, welcome the attention.

Technical exchange: please contact blogger wechat: zhuan2quan

New horizon of program
Official account " "New horizons of programs", a platform that allows you to synchronously improve your soft power and hard technology, and provides a large amount of data

Topics: Java Quartz timer