关于后端:定时任务最简单的3种实现方法

41次阅读

共计 6043 个字符,预计需要花费 16 分钟才能阅读完成。

日子匆匆穿过我而行,奔向陆地。

定时工作在理论的开发中特地常见,比方电商平台 30 分钟后主动勾销未领取的订单,以及凌晨的数据汇总和备份等,都须要借助定时工作来实现,那么咱们本文就来看一下 定时工作最简略的几种实现形式。

TOP 1:Timer

Timer 是 JDK 自带的定时工作执行类,无论任何我的项目都能够间接应用 Timer 来实现定时工作,所以 Timer 的长处就是使用方便,它的实现代码如下:

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

程序执行后果如下:

Run timerTask:Mon Aug 17 21:29:25 CST 2020
Run timerTask:Mon Aug 17 21:29:28 CST 2020
Run timerTask:Mon Aug 17 21:29:31 CST 2020

Timer 毛病剖析

Timer 类实现定时工作尽管不便,但在应用时须要留神以下问题。

问题 1:工作执行工夫长影响其余工作

当一个工作的执行工夫过长时,会影响其余工作的调度,如下代码所示:

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, 1000, 3000);
        timer.schedule(timerTask2, 1000, 3000);
    }
}

程序执行后果如下:

进入 timerTask 1:Mon Aug 17 21:44:08 CST 2020
Run timerTask 1:Mon Aug 17 21:44:13 CST 2020
Run timerTask 2:Mon Aug 17 21:44:13 CST 2020
进入 timerTask 1:Mon Aug 17 21:44:13 CST 2020
Run timerTask 1:Mon Aug 17 21:44:18 CST 2020
进入 timerTask 1:Mon Aug 17 21:44:18 CST 2020
Run timerTask 1:Mon Aug 17 21:44:23 CST 2020
Run timerTask 2:Mon Aug 17 21:44:23 CST 2020
进入 timerTask 1:Mon Aug 17 21:44:23 CST 2020

从上述后果中能够看出,当工作 1 运行工夫超过设定的间隔时间时,工作 2 也会提早执行。 本来工作 1 和工作 2 的执行工夫距离都是 3s,但因为工作 1 执行了 5s,因而工作 2 的执行工夫距离也变成了 10s(和原定工夫不符)。

问题 2:工作异样影响其余工作

应用 Timer 类实现定时工作时,当一个工作抛出异样,其余工作也会终止运行,如下代码所示:

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, 1000, 3000);
        timer.schedule(timerTask2, 1000, 3000);
    }
}

程序执行后果如下:

进入 timerTask 1:Mon Aug 17 22:02:37 CST 2020
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
    at com.example.MyTimerTask$1.run(MyTimerTask.java:21)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)
Process finished with exit code 0

Timer 小结

Timer 类实现定时工作的长处是不便,因为它是 JDK 自定的定时工作,但毛病是工作如果执行工夫太长或者是工作执行异样,会影响其余任务调度,所以在生产环境下倡议审慎应用。

TOP 2:ScheduledExecutorService

ScheduledExecutorService 也是 JDK 1.5 自带的 API,咱们能够应用它来实现定时工作的性能,也就是说 ScheduledExecutorService 能够实现 Timer 类具备的所有性能,并且它能够解决了 Timer 类存在的所有问题

ScheduledExecutorService 实现定时工作的代码示例如下:

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 执行一次
    }
}

程序执行后果如下:

Run Schedule:Mon Aug 17 21:44:23 CST 2020
Run Schedule:Mon Aug 17 21:44:26 CST 2020
Run Schedule:Mon Aug 17 21:44:29 CST 2020

ScheduledExecutorService 可靠性测试

① 工作超时执行测试

ScheduledExecutorService 能够解决 Timer 工作之间相应影响的毛病,首先咱们来测试一个工作执行工夫过长,会不会对其余工作造成影响,测试代码如下:

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());
        }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
        // 执行工作 2
        scheduledExecutorService.scheduleAtFixedRate(() -> {System.out.println("Run Schedule2:" + new Date());
        }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
    }
}

程序执行后果如下:

Run Schedule2:Mon Aug 17 11:27:55 CST 2020
进入 Schedule:Mon Aug 17 11:27:55 CST 2020
Run Schedule2:Mon Aug 17 11:27:58 CST 2020
Run Schedule:Mon Aug 17 11:28:00 CST 2020
进入 Schedule:Mon Aug 17 11:28:00 CST 2020
Run Schedule2:Mon Aug 17 11:28:01 CST 2020
Run Schedule2:Mon Aug 17 11:28:04 CST 2020

从上述后果能够看出,当工作 1 执行工夫 5s 超过了执行频率 3s 时,并没有影响工作 2 的失常执行,因而应用 ScheduledExecutorService 能够防止工作执行工夫过长对其余工作造成的影响

② 工作异样测试

接下来咱们来测试一下 ScheduledExecutorService 在一个工作异样时,是否会对其余工作造成影响,测试代码如下:

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());
        }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
        // 执行工作 2
        scheduledExecutorService.scheduleAtFixedRate(() -> {System.out.println("Run Schedule2:" + new Date());
        }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
    }
}

程序执行后果如下:

进入 Schedule:Mon Aug 17 22:17:37 CST 2020
Run Schedule2:Mon Aug 17 22:17:37 CST 2020
Run Schedule2:Mon Aug 17 22:17:40 CST 2020
Run Schedule2:Mon Aug 17 22:17:43 CST 2020

从上述后果能够看出,当工作 1 出现异常时,并不会影响工作 2 的执行

ScheduledExecutorService 小结

在单机生产环境下倡议应用 ScheduledExecutorService 来执行定时工作,它是 JDK 1.5 之后自带的 API,因而应用起来也比拟不便,并且应用 ScheduledExecutorService 来执行工作,不会造成工作间的相互影响。

TOP 3:Spring Task

如果应用的是 Spring 或 Spring Boot 框架,能够间接应用 Spring Framework 自带的定时工作,应用下面两种定时工作的实现形式,很难实现设定了具体工夫的定时工作,比方当咱们须要每周五来执行某项工作时,但如果应用 Spring Task 就可轻松的实现此需要。

以 Spring Boot 为例,实现定时工作只需两步:

  1. 开启定时工作;
  2. 增加定时工作。

具体实现步骤如下。

① 开启定时工作

开启定时工作只须要在 Spring Boot 的启动类上申明 @EnableScheduling 即可,实现代码如下:

@SpringBootApplication
@EnableScheduling // 开启定时工作
public class DemoApplication {// do someing}

② 增加定时工作

定时工作的增加只须要应用 @Scheduled 注解标注即可,如果有多个定时工作能够创立多个 @Scheduled 注解标注的办法,示例代码如下:

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("我是定时工作~");
    }
}

留神:定时工作是主动触发的无需手动干涉,也就是说 Spring Boot 启动后会主动加载并执行定时工作。

Cron 表达式

Spring Task 的实现须要应用 cron 表达式来申明执行的频率和规定,cron 表达式是由 6 位或者 7 位组成的(最初一位能够省略),每位之间以空格分隔,每位从左到右代表的含意如下:

其中 * 和 ? 号都示意匹配所有的工夫。

cron 表达式在线生成地址:https://cron.qqe2.com/

正文完
 0