Java实现定时任务的方法详解

作者:god23bin 时间:2022-12-30 04:14:00 

前言

学过定时任务,但是我忘了,忘得一干二净,害怕,一直听别人说:

你写一个定时任务就好了。

写个定时任务让他去爬取就行了。

我不会,所以现在得补回来了,欠下的终究要还的,/(ㄒoㄒ)/~~

定时任务是什么

大家都用过闹钟,闹钟可以说是一种定时任务。

比如我们设定了周一到周五早上7点半的时间响铃,那么闹钟就会在周一到周五的早上7点半进行响铃,这种就是定时的任务。时间定在 周一到周五的早上7点半 ,任务就是 响铃

那么,在 Java 中,如何实现这样的功能呢?即如何实现定时任务呢?

定时任务的有哪些是实现方式

说明:@Slf4j 注解来源于 Lombok ,需要引入 Lombok 依赖

纯手写单线程循环

单线程,写一个死循环,通过线程的睡眠(等待)完成定时任务:

@Slf4j
public class ThreadTaskDemo {
   public static void main(String[] args) {
       timer1();
   }

public static void timer1() {
       new Thread(() -> {
           while (true) {
               log.info("当前时间 {}", LocalDateTime.now());
               try {
                   // 每个1秒执行一次
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }).start();
   }
}

控制台输出:

14:02:35.893 [Thread-0] INFO cn.god23bin.demo.timer.ThreadTaskDemo - 当前时间 2022-10-25T14:02:35.892
14:02:36.904 [Thread-0] INFO cn.god23bin.demo.timer.ThreadTaskDemo - 当前时间 2022-10-25T14:02:36.904
14:02:37.907 [Thread-0] INFO cn.god23bin.demo.timer.ThreadTaskDemo - 当前时间 2022-10-25T14:02:37.907

可以看到每秒执行一次,成功实现了定时任务。

Timer 和它的小伙伴

在 java.util 包下,有这么 3 个东西,分别是 Timer 类、TimerTask 接口、TimerThread 类,这 3 个东西就可以帮我们实现定时任务。

Timer 有这么 2 个方法(当然不止这两个,还有 4 个相关的):

// 延迟delay秒后执行task任务
public void schedule(TimerTask task, long delay);
// 延迟delay秒后,以period间隔时间执行task任务
public void schedule(TimerTask task, long delay, long period);

TimerTask 有个 run() 抽象方法,那我们可以实现这个抽象方法作为我们的任务逻辑,由于 TimerTask 是接口,需要一个类实现它,那我们下面就用匿名内部类的方式来实现这个接口。代码如下:

@Slf4j
public class TimerDemo {

public static void main(String[] args) {
       timer1();
   }

public static void timer1() {
       // 单线程
       Timer timer = new Timer();
       log.info("1秒后执行任务A,A完成后,等待1秒开始定时执行任务B,当前时间 {}", LocalDateTime.now());
       // 1秒后执行
       timer.schedule(new TimerTask() {
           @Override
           public void run() {
               log.info("任务A 当前时间 {}", LocalDateTime.now());
           }
       }, 1000); // 这里 1000,就是代表延迟 1000 毫秒后再执行

// 每隔2秒执行一次这个任务
       timer.schedule(new TimerTask() {
           @Override
           public void run() {
               log.info("定时任务B 当前时间 {}", LocalDateTime.now());
           }
       }, 1000, 2000); // 1000 同理,2000 即执行完本次任务后,隔 2000 毫秒后再一次执行,达到定时任务的效果
   }

}

控制台输出:

14:09:37.416 [main] INFO cn.god23bin.demo.timer.TimerDemo - 1秒后执行任务A,A完成后,等待1秒开始定时执行任务B,当前时间 2022-10-25T14:09:37.415
14:09:38.428 [Timer-0] INFO cn.god23bin.demo.timer.TimerDemo - 任务A 当前时间 2022-10-25T14:09:38.428
14:09:38.428 [Timer-0] INFO cn.god23bin.demo.timer.TimerDemo - 定时任务B 当前时间 2022-10-25T14:09:38.428
14:09:40.443 [Timer-0] INFO cn.god23bin.demo.timer.TimerDemo - 定时任务B 当前时间 2022-10-25T14:09:40.443
14:09:42.457 [Timer-0] INFO cn.god23bin.demo.timer.TimerDemo - 定时任务B 当前时间 2022-10-25T14:09:42.457

从控制台输出的时间可以看到,任务 A 是只执行了一次,因为我们没有传递 period 参数给 schedule () 方法。而任务 B 是一个定时任务,因为传递了 period 参数,period 参数为 2000,即 2000 毫秒。

所以,任务 B 会每隔 2 秒执行一次。到这里,我们通过 Timer 实现了定时任务。下面看看基于多线程的 ScheduledExecutorService 接口。

ScheduledExecutorService

ScheduledExecutorService 接口位于 java.util.concurrent 包中,是继承 ExecutorService 接口的。

这个接口有 4 个抽象方法(先了解一下):

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

从上面的抽象方法可以看到,第一个参数是 Runnable 接口或 Callable 接口,这里就是写任务逻辑的,后面的 delay 也和之前的意思一样,延迟多少时间才开始执行这个定时任务,unit 主要是指定 long 参数的时间单位。period 也是一样的意思,间隔多少秒(周期)才执行下一次的任务。

ExecutorService 接口表述了异步执行的机制,并且可以让任务在后台执行。ExecutorService 接口的实现类有我们知道的 ThreadPoolExecutor (不知道的话,现在就知道啦)。

基本使用

那我们如何获取ScheduledExecutorService的实现类?如何使用它实现定时任务?

可以通过 Executors.newSingleThreadScheduledExecutor() 获取其实现类,然后调用 schedule() 方法实现定时任务。

现在先看一下,如何使用:

@Slf4j
public class ScheduledExecutorServiceDemo {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
       testScheduledExecutorService();
   }

public static void testScheduledExecutorService() throws ExecutionException, InterruptedException {
       ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
       log.info("2秒后开始执行任务,此刻时间---{}", LocalDateTime.now());
       ScheduledFuture<?> future = scheduledExecutorService.schedule(() -> {
           log.info("任务开始---{}", LocalDateTime.now());
           try {
               Thread.sleep(2000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           log.info("任务结束---{}", LocalDateTime.now());
           return "ok";
       }, 2000, TimeUnit.MILLISECONDS); // 延迟 2 秒后执行
       log.info("任务执行后 future {}, 时间 {}", future.get(), LocalDateTime.now());
   }
}

控制台输出:

14:15:44.510 [main] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 2秒后开始执行任务,此刻时间---2022-10-25T14:15:44.509
14:15:46.524 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务开始---2022-10-25T14:15:46.524
14:15:48.537 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务结束---2022-10-25T14:15:48.537
14:15:48.538 [main] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务执行后 future ok, 时间 2022-10-25T14:15:48.538

很明显,这里不是一个定时任务,因为只执行了一次就结束了,所以我们需要调用两外两个来实现,分别是 scheduleAtFixedRate() 方法和 scheduleWithFixedDelay() 方法。

固定频率触发定时任务

scheduleAtFixedRate() 方法,可以固定多久就触发一次任务。下面我们写一个延迟 2 秒后开始执行任务,经过 5 秒后再执行下一次的任务的代码:

@Slf4j
public class ScheduledExecutorServiceDemo {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
       testFixedRate();
   }

public static void testFixedRate() {
       ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
       log.info("2秒后开始执行任务,此刻时间---{}", LocalDateTime.now());
       // 固定频率(每隔5秒)开始执行一个任务
       scheduledExecutorService.scheduleAtFixedRate(() -> {
           log.info("任务开始---{}", LocalDateTime.now());
           try {
               Thread.sleep(2000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           log.info("任务结束---{}", LocalDateTime.now());
       }, 2000, 5000, TimeUnit.MILLISECONDS);
   }
}

通过 Thread.sleep(2000) 模拟任务执行了 2 秒的时间,控制台输出如下:

14:17:15.081 [main] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 2秒后开始执行任务,此刻时间---2022-10-25T14:17:15.079
14:17:17.094 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务开始---2022-10-25T14:17:17.094
14:17:19.109 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务结束---2022-10-25T14:17:19.109
14:17:22.094 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务开始---2022-10-25T14:17:22.094
14:17:24.106 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务结束---2022-10-25T14:17:24.106
14:17:27.090 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务开始---2022-10-25T14:17:27.090
14:17:29.099 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务结束---2022-10-25T14:17:29.099

可以看到,第一次执行的时间是 14:17:17 ,即第 17 秒,下一次执行的时间是 14:17:22 ,即第 22 秒,这个过程经过了 5 秒钟,这就是 scheduleAtFixedRate() 方法的效果。

固定延迟触发定时任务

scheduleWithFixedDelay() 方法,可以固定任务完成后延迟多久才执行下一次任务。下面我们写一个延迟 1 秒后开始执行定时任务,当任务完成后,延迟 4 秒再执行下一次任务。代码如下:

@Slf4j
public class ScheduledExecutorServiceDemo {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
       testFixedDelay();
   }

public static void testFixedDelay() {
       ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
       log.info("1秒后开始执行任务,此刻时间---{}", LocalDateTime.now());
       // 任务完成后间隔4秒开始执行下一次任务
       scheduledExecutorService.scheduleWithFixedDelay(() -> {
           log.info("任务开始---{}", LocalDateTime.now());
           try {
               Thread.sleep(2000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           log.info("任务结束---{}", LocalDateTime.now());
       }, 1000, 4000, TimeUnit.MILLISECONDS);
   }
}

通过 Thread.sleep(2000) 模拟任务执行了 2 秒的时间,控制台输出如下:

14:20:31.352 [main] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 1秒后开始执行任务,此刻时间---2022-10-25T14:20:31.351
14:20:32.370 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务开始---2022-10-25T14:20:32.370
14:20:34.371 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务结束---2022-10-25T14:20:34.371
14:20:38.379 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务开始---2022-10-25T14:20:38.379
14:20:40.381 [pool-1-thread-1] INFO cn.god23bin.demo.timer.ScheduledExecutorServiceDemo - 任务结束---2022-10-25T14:20:40.381

可以看到,第一次任务结束的时间是 14:20:34 ,即第 34 秒,下一次执行的时间是 14:17:38 ,即第 38 秒,这个过程经过了 4 秒钟,这就是 scheduleWithFixedDelay() 方法的效果。

来源:https://www.cnblogs.com/god23bin/p/I-can-finally-write-scheduled-tasks-in-Java.html

标签:Java,定时,任务
0
投稿

猜你喜欢

  • java基本教程之synchronized关键字 java多线程教程

    2023-02-20 04:11:17
  • springcloud gateway如何实现路由和负载均衡

    2023-01-09 07:13:35
  • Spring Cloud之服务监控turbine的示例

    2023-04-20 23:26:44
  • Java设计模式之观察者模式(Observer模式)介绍

    2022-10-16 04:40:42
  • Java操作redis设置第二天凌晨过期的解决方案

    2022-11-15 11:40:10
  • 如何用.NETCore操作RabbitMQ

    2022-06-20 04:11:04
  • 浅析Java编程中枚举类型的定义与使用

    2021-07-04 23:46:16
  • 使用Mybatis-Plus时的SqlSessionFactory问题及处理

    2022-01-30 07:40:04
  • C#复杂XML反序列化为实体对象两种方式小结

    2022-08-05 16:50:45
  • mybatis中使用InsertProvider注解报错解决全过程

    2021-06-25 18:36:42
  • SpringBoot启动访问localhost:8080报错404的解决操作

    2021-07-20 08:44:00
  • C#中括号强转、as、is区别详解

    2023-05-07 03:04:45
  • 深入理解Java中的克隆

    2023-03-14 02:59:14
  • java接口Array介绍

    2021-06-04 16:01:25
  • Servlet 过滤器详细介绍

    2021-10-21 13:51:57
  • Android平台预置GMS包后关机闹钟失效问题及解决方法

    2022-12-31 05:52:33
  • Spring Boot集成Ehcache缓存解决方式

    2023-05-13 08:27:27
  • C#日期格式强制转换方法(推荐)

    2022-09-19 10:24:57
  • SpringRetry重试框架的具体使用

    2022-09-21 00:37:35
  • springmvc和js前端的数据传递和接收方式(两种)

    2021-06-15 08:33:37
  • asp之家 软件编程 m.aspxhome.com