Java定时任务

定时任务

Timer

JDK​自带的Timer​和TimerTask​可用于创建定时任务, 其中TimerTask继承了Runnable接口, 重写runnable接口就行. 观察源码可知, 构造函数中启动了一个线程, 执行一个while(true)循环, 不断从任务队列中取出任务执行, 但队列为空就退出.

关键方法

  • schedule(TimerTask task,long delay):​ 延迟delay毫秒执行一次task (执行完程序不结束)

  • schedule(TimerTask task, Date time):​在指定时间执行指定的任务。(只执行一次)

  • schedule(TimerTask task,long delay,long period):​ 延迟delay毫秒之后,以指定的间隔period毫秒重复执行指定的任务

  • schedule(TimerTask task, Date firstTime , long period):​在指定的时间开始按照指定的间隔(period)重复执行指定的任务

  • scheduleAtFixedRate(TimerTask task,Date firstTime,long period);​ 在指定的时间开始进行重复的固定速率执行任务

  • scheduleAtFixedRate(TimerTask task,long delay,long period);​ 在指定的延迟后开始进行重复的固定速率执行任务

  • cancel():​终止计时器, 清空任务队列(终止所有任务)

  • purge():​​移除队列中cancel态TimerTask​​(并不会终止任务)

schedule

其中schedule(TimerTask task,long delay,long period)​比较常用, 下面测试两种场景, 先创建一个大约执行3s的任务

private static TimerTask getTask() {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("current time " + new Date());
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("task is over");
            }
        };
        return timerTask;
}

任务耗时costTime比period​短

main方法中调用如下方法, 发现间隔时间为​period

 public static void timerTask1() {
        TimerTask timerTask = getTask();
        Timer timer = new Timer();
        System.out.println("start time " + new Date());
        timer.schedule(timerTask, 0, 5000L);
}
------------------------------------
start time Sun Mar 26 19:45:11 CST 2023
current time Sun Mar 26 19:45:11 CST 2023
task is over
current time Sun Mar 26 19:45:16 CST 2023
task is over

任务耗时costTime比period​长

main方法中调用如下方法, 发现间隔时间为costTime

public static void timerTask1() {
        TimerTask timerTask = getTask();
        Timer timer = new Timer();
        System.out.println("start time " + new Date());
        timer.schedule(timerTask, 0, 2000L);
}
--------------------------------
start time Sun Mar 26 19:48:50 CST 2023
current time Sun Mar 26 19:48:50 CST 2023
task is over
current time Sun Mar 26 19:48:53 CST 2023
task is over

所以schedule的执行策略保证将任务执行完毕, 再执行下一趟, 两次任务间隔之间多久并不能保证

scheduleAtFixedRate

scheduleAtFixedRate(TimerTask task,Date firstTime,long period);​和schedule做完全一样的测试

任务耗时costTime比period​短

main方法中调用如下方法, 发现间隔时间为​period

public static void timerTask2() {
        TimerTask timerTask = getTask();
        getTask();
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(timerTask, 2, 5000L);
}
-------------------------------------
current time Sun Mar 26 19:54:33 CST 2023
task is over
current time Sun Mar 26 19:54:38 CST 2023
task is over

任务耗时costTime比period​长

main方法中调用如下方法, 发现间隔时间为costTime

public static void timerTask2() {
        TimerTask timerTask = getTask();
        getTask();
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(timerTask, 2, 2000L);
}
-----------------------------------------
current time Sun Mar 26 19:57:56 CST 2023
task is over
current time Sun Mar 26 19:57:59 CST 2023
task is over

schedule和scheduleAtFixedRate区别

上面测试, 发现二者效果完全一样啊, 那么为啥有两种功能类似的方法呢? 差别在于, 如果第二个参数为Date, 指定开始执行的时间在当前系统运行时间之前,scheduleAtFixedRate会把已经过去的时间也作为周期执行,而schedule不会把过去的时间算上。日常用延时,固定间隔那种方式, 发现是一样的.

Timer的缺陷

Timer开启的时单线程, 若存在多个任务, 则任务执行时间有干扰.

public static void timerTask3() {
        Timer timer = new Timer();
        TimerTask timerTask1 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task1 current time " + new Date());
            }
        };

        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 current time " + new Date());
                try {
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        timer.scheduleAtFixedRate(timerTask1, 0, 3000L);
        timer.scheduleAtFixedRate(timerTask2, 0, 2000L);
}
==================================
task1 current time Sun Mar 26 20:26:44 CST 2023
	task2 current time Sun Mar 26 20:26:44 CST 2023
	task2 current time Sun Mar 26 20:26:49 CST 2023
task1 current time Sun Mar 26 20:26:54 CST 2023
	task2 current time Sun Mar 26 20:26:54 CST 2023

任务2执行耗时比较久, 导致任务间隔时间不符合预期

终止任务

由于是单线程, 所以Timer适合执行单任务, 想要终止任务, 直接调用cancel​即可. 在实际开发中, 有的人通过map记录TimerTask, 想关闭任务的时候从map移除对应的key, 但这种方式是只是将TimerTask从map中删除, 并没有关闭定时任务.

ScheduledExecutorService

ScheduledExecutorService是Java中另一个用于创建定时任务的类。可以使用它创建和执行定期执行的任务,并且可以控制任务的执行频率。若多个任务, 可开启多个线程执行.

scheduleAtFixedRate

任务耗时costTime比period​短

间隔时间为​period

  private static void scheduledExecutorServiceTask() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        Runnable task = () -> {
            System.out.println("current time " + new Date());
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello timer");
        };
        scheduledExecutorService.scheduleAtFixedRate(task, 0, 5, TimeUnit.SECONDS);
}
=====================================
current time Sun Mar 26 20:15:09 CST 2023
hello timer
current time Sun Mar 26 20:15:14 CST 2023
hello timer

任务耗时costTime比period​长

把period改为2s, 间隔时间为costTime

current time Sun Mar 26 20:17:15 CST 2023
hello timer
current time Sun Mar 26 20:17:18 CST 2023
hello timer

scheduleWithFixedDelay

这个注重的是间隔时间, 即任务执行完毕开始计时, 等待delay=2再开始下一次执行.

 private static void scheduledExecutorServiceTask() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        Runnable task = () -> {
            System.out.println("start time " + new Date());
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("end time " + new Date());
        };
        scheduledExecutorService.scheduleWithFixedDelay(task, 0, 2, TimeUnit.SECONDS);
}
================================
start time Sun Mar 26 20:34:03 CST 2023
end time Sun Mar 26 20:34:06 CST 2023
start time Sun Mar 26 20:34:08 CST 2023
end time Sun Mar 26 20:34:11 CST 2023
start time Sun Mar 26 20:34:13 CST 2023

终止任务

终止所有任务调用shutdown关闭线程池就行, 但是想根据条件关闭单个任务咋办

 public static void test2() throws InterruptedException {
        final String jobID1 = "my_job_1";
        final String jobID2 = "my_job_2";
        AtomicBoolean isCanceled1 = new AtomicBoolean(false);
        AtomicBoolean isCanceled2 = new AtomicBoolean(false);
        final Map<String, Future> taskMap = new HashMap<>();

        Future future1 = scheduler.scheduleWithFixedDelay(() -> {
            System.out.println("+++++++jobID1");
            if (isCanceled1.get()) {
                Future future = taskMap.get(jobID1);
                if (future != null) {
                    System.out.println("task1 is shutdown");
                    future.cancel(true);
                }
            }

        }, 0, 1, TimeUnit.SECONDS);


        Future future2 = scheduler.scheduleWithFixedDelay(() -> {
                    System.out.println("=====jobID2 " + new Date());
                    if (isCanceled2.get()) {
                        Future future = taskMap.get(jobID2);
                        if (future != null) {
                            System.out.println("task2 is shutdown");
                            future.cancel(true);
                        }
                    }
                }
                , 0, 1, TimeUnit.SECONDS);


        taskMap.put(jobID1, future1);
        taskMap.put(jobID2, future2);

        Thread.sleep(5000L);
        isCanceled1.set(true);
        Thread.sleep(2000L);
        isCanceled2.set(true);
        Thread.sleep(1000L);  // 留点时间让任务2自己关闭
        if (isCanceled1.get() && isCanceled2.get()) {
            System.out.println("pool is shutdown");
            scheduler.shutdown();
        }
}

通过以上方式可以终止单个任务, 当没有任务执行,可以将线程池关闭

+++++++jobID1
=====jobID2 Sun Mar 26 21:02:29 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:30 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:31 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:32 CST 2023
+++++++jobID1
=====jobID2 Sun Mar 26 21:02:33 CST 2023
+++++++jobID1
task1 is shutdown
=====jobID2 Sun Mar 26 21:02:34 CST 2023
=====jobID2 Sun Mar 26 21:02:35 CST 2023
=====jobID2 Sun Mar 26 21:02:36 CST 2023
task2 is shutdown
pool is shutdown

进程已结束,退出代码0

Spring task的@schedule

每隔1s执行一次.

public class ScheduledAnnotationExample { 
    @Scheduled(fixedRate = 1000) 
    public void printMessage() { 
        System.out.println("hello timer"); 
    } 
}

上面几种方式都会将任务执行完毕, 而不会因为任务时间耗时长就提前调度下一个任务. 还有其他几种方法 开发应该知道的定时任务——Java定时任务 - 掘金 (juejin.cn)

posted @ 2023-03-26 21:09  shmilyt  阅读(93)  评论(0编辑  收藏  举报