Java 中的定时任务(一)

定时任务简单来说就是在指定时间,指定的频率来执行一个方法,而在 Java 中我们又该如何实现呢?

 

想来主要有 3 种方式,最原始的方式肯定是开启一个线程,让它睡一会跑一次睡一会跑一次这也就达到了定频率的执行 run 方法,我们只需要将业务逻辑写在 run 方法中即可。这种方式总结就是单个线程来执行单个任务。

方式一:创建一个线程

 

package com.yu.task;

import java.util.Date;

public class ThreadTest {

    public static void main(String[] args) {
        // 设置执行周期
        final long timeInterval = 3000;
        
        Runnable runnable = new Runnable() {
            public void run() {
                while (true) {
                    System.out.println("Task Run ... " + new Date());
                    
                    try {
                        Thread.sleep(timeInterval);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

 

第二种方式:使用 JDK 自带的 API Timer 以及 TaskTimer。 

这种方式和第一种简单粗暴的方式有什么区别呢,主要体现在使用 API 可以在指定的时间开始启动任务,可以延期执行首次任务,同样也看可以设置一定的时间间隔,但是原理是是一样的,后台还是启动了一个线程,应该说是只有一个线程在执行任务,不管我们启动的 Task 有几个。所以这也会有问题,比方说一个一个任务没有执行完成,另一个任务就开始执行了,可能会发生并发问题。还有若是一个任务中报错,则线程就会被停止。

 

package com.yu.task;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class MyTask extends TimerTask{

    private String name;
    
    public MyTask(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = sf.format(new Date());
        System.out.println("exec MyTask ... 当前时间为:" + format);
        System.out.println(this.name +" 正在执行!" + sf.format(new Date()));
    }
    
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task1 = new MyTask("Tasks 1");
        TimerTask task2 = new MyTask("Tasks 2");
        
        Calendar calendar1 = Calendar.getInstance();
        calendar1.add(Calendar.SECOND, 3);
        Calendar calendar2 = Calendar.getInstance();
        calendar2.add(Calendar.SECOND, 5);
        
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = sf.format(new Date());
        System.out.println("当前时间为:" + format);
        
        timer.schedule(task1, calendar1.getTime(), 3000L);
        timer.schedule(task2, calendar2.getTime(), 3000L);
    }

}

 

其实在 Timer 中,封装了一个 Task 的队列和 Time 的线程对象,我们自定义的 Task 的引用会放在队列中等待执行。

大致是这么一个关系 Timer -  TimerThread - TaskQueue - MyTask - run  当然最终执行的方法肯定是我们自定义任务中的 run 方法。因为我们自定义的任务已经继承了 TimeTask ,而这个类已经实现了 Runnable 接口。

 

Timer 定时器第一种方式好的地方还在于可以选择关闭任务,查看任务的执行情况等。下面介绍几个相关的方法。

启动定时任务也有几个不同的方法,每个都有不同的使用场景。

上面是 Timer 类中的属性和方法的简图,开启一个任务我们主要使用 6 个方法, 一共 3 组,先看简单的

schedule() 方法中含有两个参数,主要是用来执行一次任务的,不存在频率的问题,而 3 个参数表示执行什么任务,什么时候开始执行/延时多久执行,多久执行一次。

 

现在来假设一个场景,01秒开始执行一个任务,频率是 3 秒执行一次,不知道你们有没有想到这个情况,若是这个方法本身执行需要 4 秒,那第二次执行的时间是 04 呢? 还是 05 秒呢?

 

而这个差别就是 schedule 和 scheduleAtFixedRate 方法的区别,前者会按照任务执行的情况来执行下一次任务,也就是说 01 之后,会等第一次执行结束再开始第二次,这样就会带来一个问题,每次执行时间都比预想的要晚。而 scheduleAtFixedRate 则不存在这个问题,它会按照指定的时间和频率执行,这样坐就会出现,同一时刻会有两个任务在同时执行,若是,可能会发生并发问题。

 

上面讨论的是当任务本身执行的时间大于频率时,两个方法不同的执行情况,还有一个情况,若是当前时间晚于我们设定的开始执行时间又会怎么办呢?

 

schedule () 会比较符合正常思维,晚了就晚了,现在开始执行就是,不存在补回的情况,但是 scheduleAtFixedRate 则会一次性补回未执行的次数。举例来说,当前时间为 09,而我们设定的开始时间为 00 ,频率为 3 秒,则 schedule 会在 09 执行一次,12 执行一次。而 scheduleAtFixedRate 会在 09 一次性执行 4 次,12 执行一次,后面就是正常的频率。

 

下面再说几个可能会起到锦上添花作用的方法,我们的若是想取消任务的执行,有一个方法但是分为两个类执行,我们可以调用 TimeTask 中的 cancel 方法,这个方法只对当前任务有效,若是想取消全部的任务,则需要调用 Timer 中的 cancel 方法。

 

那有该如何查看定时器 Timer 中已经被取消任务的数量呢?当然还是有方法的,那就是 Timer 中的 purge 方法。

 

 说了这么说,实际上我们可以看到,Timer 定时器本身还是调用线程来完成定时操作。且后台只有一个多线程 TimeThread 在工作。

 

那 Timer 定时器有什么缺点呢?

1 并发操作时的缺陷,这是因为 Timer 的后台只有一个执行线程导致的,容易引起并发问题。

2 任务抛出异常时缺陷。如果 TimeTask 抛出 RuntimeException,Timer 会停止所有任务的执行。

 

所以以后我们在使用 Timer 定时器的时候要注意,这两个情况,多任务且并发执行的时候不要使用 Timer,复杂任务调度的时候也不要使用 Timer ,因为一个不小心出现异常了,所有任务都卡壳了。但是,Timer 定时器处理一些简单的定是任务还是非常方便的!比方说我想实现的,定时发送邮件。用起来就很是方便,因为这是 JDK 自带的 API 呀!

 

第三种方式:使用 Java 中的专门用于定时任务管理的框架 Quartz 。(还没学,等等吧……)

 

上面也说了 Timer 定时器的弊端,怎么办,据听说 Quartz 可以解决这些……

 

后记:据我知道,Java 中定时任务也就这几个吧,欢迎补充,有好多人说到某某框架中有定时任务,我想说的是,那不是!那是框架集成了上面说的定时任务框架,可能集成的就是 Quartz,或是 Quartz 的简化版本……

posted on 2018-12-06 19:28 余同学的开发之路 阅读(...) 评论(...) 编辑 收藏

导航

公告