Three simplest implementation methods of timing tasks (super practical)

Scheduled tasks are particularly common in actual development. For example, the e-commerce platform automatically cancels unpaid orders after 30 minutes, and the data summary and backup in the early morning need to be realized with the help of scheduled tasks. In this paper, let's take a look at the simplest implementation methods of scheduled tasks.

TOP 1:Timer

Timer is the JDK's own scheduled task execution class. Any project can directly use timer to implement scheduled tasks. Therefore, timer has the advantage of convenient use. Its implementation code is as follows:

public class MyTimerTask {
    public static void main(String[] args) {
        // 定义一个任务
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask:" + new Date());
            }
        };
        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
        timer.schedule(timerTask,1000,3000);
    }
}

The program execution results are as follows:

Timer defect analysis

Although it is convenient for timer class to implement timed tasks, the following problems should be paid attention to when using it.

Question 1: the long execution time of tasks affects other tasks

When the execution time of a task is too long, it will affect the scheduling of other tasks, as shown in the following code:

public class MyTimerTask {
    public static void main(String[] args) {
        // 定义任务 1
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("进入 timerTask 1:" + new Date());
                try {
                    // 休眠 5 秒
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Run timerTask 1:" + new Date());
            }
        };
        // 定义任务 2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask 2:" + new Date());
            }
        };
        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
        timer.schedule(timerTask,3000);
        timer.schedule(timerTask2,3000);
    }
}

The program execution results are as follows:

It can be seen from the above results that when the running time of task 1 exceeds the set interval, the execution of task 2 will also be delayed. The original execution interval of task 1 and task 2 was 3S, but since task 1 was executed for 5S, the execution interval of task 2 also became 10s (inconsistent with the original time).

Problem 2: abnormal task affects other tasks

When using timer class to implement timed tasks, when one task throws an exception, other tasks will also terminate, as shown in the following code:

public class MyTimerTask {
    public static void main(String[] args) {
        // 定义任务 1
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("进入 timerTask 1:" + new Date());
                // 模拟异常
                int num = 8 / 0;
                System.out.println("Run timerTask 1:" + new Date());
            }
        };
        // 定义任务 2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask 2:" + new Date());
            }
        };
        // 计时器
        Timer timer = new Timer();
        // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
        timer.schedule(timerTask,3000);
    }
}

The program execution results are as follows:

Timer summary

Timer class has the advantage of convenience in implementing scheduled tasks, because it is a JDK customized scheduled task, but the disadvantage is that if the task execution time is too long or the task execution is abnormal, it will affect the scheduling of other tasks. Therefore, it is recommended to use it carefully in the production environment.

TOP 2:scheduledexecutorservice

Scheduledexecutorservice is also the API of JDK 1.5. We can use it to realize the function of scheduled tasks, that is, scheduledexecutorservice can realize all the functions of timer class, and it can solve all the problems of timer class.

The code example of scheduled executorservice implementing scheduled tasks is as follows:

public class Myscheduledexecutorservice {
    public static void main(String[] args) {
        // 创建任务队列
        scheduledexecutorservice scheduledexecutorservice =
                Executors.newScheduledThreadPool(10); // 10 为线程数量
		// 执行任务
        scheduledexecutorservice.scheduleAtFixedRate(() -> {
            System.out.println("Run Schedule:" + new Date());
        },1,3,TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
    }
}

The program execution results are as follows:

Scheduled executorservice reliability test

① Task timeout execution test

Scheduledexecutorservice can solve the problem of corresponding impact between timer tasks. First, let's test whether the execution time of one task is too long, which will affect other tasks. The test code is as follows:

public class Myscheduledexecutorservice {
    public static void main(String[] args) {
        // 创建任务队列
        scheduledexecutorservice scheduledexecutorservice =
                Executors.newScheduledThreadPool(10);
        // 执行任务 1
        scheduledexecutorservice.scheduleAtFixedRate(() -> {
            System.out.println("进入 Schedule:" + new Date());
            try {
                // 休眠 5 秒
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Run Schedule:" + new Date());
        },TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
        // 执行任务 2
        scheduledexecutorservice.scheduleAtFixedRate(() -> {
            System.out.println("Run Schedule2:" + new Date());
        },TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
    }
}

The program execution results are as follows:

It can be seen from the above results that when the execution time 5S of task 1 exceeds the execution frequency 3S, the normal execution of task 2 is not affected. Therefore, using scheduled executorservice can avoid the impact of too long task execution time on other tasks.

② Task exception test

Next, let's test whether the scheduledexecutorservice will affect other tasks when one task is abnormal. The test code is as follows:

public class Myscheduledexecutorservice {
    public static void main(String[] args) {
        // 创建任务队列
        scheduledexecutorservice scheduledexecutorservice =
                Executors.newScheduledThreadPool(10);
        // 执行任务 1
        scheduledexecutorservice.scheduleAtFixedRate(() -> {
            System.out.println("进入 Schedule:" + new Date());
            // 模拟异常
            int num = 8 / 0;
            System.out.println("Run Schedule:" + new Date());
        },TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
    }
}

The program execution results are as follows:

It can be seen from the above results that when an exception occurs in task 1, the execution of task 2 will not be affected.

Scheduledexecutorservice summary

In the stand-alone production environment, it is recommended to use scheduledexecutorservice to execute scheduled tasks. It is an API built after JDK 1.5, so it is also convenient to use. Moreover, using scheduledexecutorservice to execute tasks will not cause interaction between tasks.

TOP 3:Spring Task

If you use spring or spring boot framework, you can directly use the timing tasks provided by the spring framework. Using the above two implementation methods of timing tasks, it is difficult to implement the timing tasks with specific time. For example, when we need to execute a task every Friday, but if you use spring task, you can easily implement this requirement.

Taking spring boot as an example, it only takes two steps to implement the scheduled task:

The specific implementation steps are as follows.

① Start scheduled task

To start the scheduled task, you only need to declare @ enableshcheduling on the startup class of spring boot. The implementation code is as follows:

@SpringBootApplication
@EnableScheduling // 开启定时任务
public class DemoApplication {
    // do someing
}

② Add scheduled task

To add a scheduled task, you only need to use the @ scheduled annotation annotation. If there are multiple scheduled tasks, you can create multiple @ scheduled annotation annotations. The example code is as follows:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component // 把此类托管给 Spring,不能省略
public class TaskUtils {
    // 添加定时任务
    @Scheduled(cron = "59 59 23 0 0 5") // cron 表达式,每周五 23:59:59 执行
    public void doTask(){
        System.out.println("我是定时任务~");
    }
}

Note: the scheduled task is triggered automatically without manual intervention, that is, the scheduled task will be loaded and executed automatically after spring boot is started.

Cron expression

The implementation of spring task needs to use cron expression to declare the execution frequency and rules. Cron expression is composed of 6 or 7 bits (the last bit can be omitted). Each is separated by a space. The meaning of each from left to right is as follows:

Cron expression online generation address: https://cron.qqe2.com/

Knowledge extension: distributed timed tasks

The above methods are all about the implementation of single machine scheduled tasks. If it is a distributed environment, redis can be used to implement scheduled tasks.

The methods of using redis to implement delayed tasks can be divided into two categories: through Zset and key space notification.

① Zset implementation

The idea of realizing the scheduled task through Zset is to store the scheduled task in the Zset set, and store the expiration time in the score field of Zset, and then judge whether there is a scheduled task to be executed within the current time through a wireless loop. If so, execute it. The specific implementation code is as follows:

import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;

public class DelayQueueExample {
    // zset key
    private static final String _KEY = "myTaskQueue";
    
    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = JedisUtils.getJedis();
        // 30s 后执行
        long delayTime = Instant.Now().plusSeconds(30).getEpochSecond();
        jedis.zadd(_KEY,delayTime,"order_1");
        // 继续添加测试数据
        jedis.zadd(_KEY,Instant.Now().plusSeconds(2).getEpochSecond(),"order_2");
        jedis.zadd(_KEY,"order_3");
        jedis.zadd(_KEY,Instant.Now().plusSeconds(7).getEpochSecond(),"order_4");
        jedis.zadd(_KEY,Instant.Now().plusSeconds(10).getEpochSecond(),"order_5");
        // 开启定时任务队列
        doDelayQueue(jedis);
    }

    /**
     * 定时任务队列消费
     * @param jedis Redis 客户端
     */
    public static void doDelayQueue(Jedis jedis) throws InterruptedException {
        while (true) {
            // 当前时间
            Instant NowInstant = Instant.Now();
            long lastSecond = NowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间
            long NowSecond = NowInstant.getEpochSecond();
            // 查询当前时间的所有任务
            Set<String> data = jedis.zrangeByscore(_KEY,lastSecond,NowSecond);
            for (String item : data) {
                // 消费任务
                System.out.println("消费:" + item);
            }
            // 删除已经执行的任务
            jedis.zremrangeByscore(_KEY,NowSecond);
            Thread.sleep(1000); // 每秒查询一次
        }
    }
}

② Key space notification

We can implement scheduled tasks through redis's key space notification. Its implementation idea is to set an expiration time for all scheduled tasks. After expiration, we can perceive that the scheduled tasks need to be executed by subscribing to the expiration message. At this time, we can execute the scheduled tasks.

By default, redis does not enable keyspace notifications. It needs to be enabled manually through the command of config set notify keyspace events ex. after enabling, the code of scheduled tasks is as follows:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;

public class TaskExample {
    public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedis();
        // 执行定时任务
        doTask(jedis);
    }

    /**
     * 订阅过期消息,执行定时任务
     * @param jedis Redis 客户端
     */
    public static void doTask(Jedis jedis) {
        // 订阅过期消息
        jedis.psubscribe(new JedisPubSub() {
            @Override
            public void onPMessage(String pattern,String channel,String message) {
                // 接收到消息,执行定时任务
                System.out.println("收到消息:" + message);
            }
        },_TOPIC);
    }
}

For more information on the implementation of scheduled tasks, please click "summary of the most complete implementation methods of delayed tasks in history! With code".

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>