但行好事
莫论前程❤

定时任务常见实现方式

挚爱阅读(181)

本文将介绍三种常用的实现定时任务的方法,希望能给大家在日常项目中带来一些启示和帮助。

一、Timer类

在java中一个完整的定时任务需要由Timer和TimerTask两个类配合完成。

  • 其中Timer是一种工具,线程用其安排在后台线程中执行的任务,可安排任务执行一次或者定期重复执行;

  • 而TimerTask是由Timer安排执行一次或者重复执行的任务。

Timer中提供了四个构造方法:

  • schedule(TimerTask task, Date time) ——安排在指定的时间执行指定的任务;
  • schedule(TimerTask task, Date firstTime, long period) ——安排指定的任务在指定的时间开始进行重复的固定延迟执行;
  • schedule(TimerTask task, long delay) ——安排在指定延迟后执行指定的任务;
  • schedule(TimerTask task, long delay, long period) ——安排指定的任务在指定的延迟后开始进行重复的固定速率执行。
1、在指定延迟时间执行定时任务
public class TestUserTimer {
    public static void main(String[] args) {
        System.out.println("任务开始" +  new Date());
        timer();
    }

//    方法一;设定指定任务task在指定时间time执行schedule(TimeerTask,Date time)
    private static void timer(){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("2秒后执行一次该任务" + new Date());
            }
        },2000);   //单位毫秒ms,延迟2秒后执行
    }
}

执行结果:

任务开始Thu May 31 14:37:33 CST 2018
2秒后执行一次该任务Thu May 31 14:37:35 CST 2018

2. 在指定时间执行定时任务
public class TestUserTimer {
    public static void main(String[] args) {
        System.out.println("指定的时间为 14:47");
        timer();
    }

//    方法二;在指定时间执行任务
    private static void timer(){
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY,14);
        calendar.set(Calendar.MINUTE,47);
        calendar.set(Calendar.SECOND,0);
        final Date time  = calendar.getTime();
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Date date = new Date(this.scheduledExecutionTime());
                System.out.println("14:47时执行一次该任务: " + date);
            }
        },time);
    }
}

运行结果为:

指定的时间为 14:47
14:47时执行一次该任务: Thu May 31 14:47:00 CST 2018

  • Date 日期类 在JDK1.0中,Date类是唯一的一个代表时间的类,但是由于Date类不便于实现国际化,所以从JDK1.1版本开始,推荐使用Calendar类进行时间和日期处理。这里简单介绍一下Date类的使用。
    • 输出格式: Thu May 31 14:47:00 CST 2018
    • 使用Date类代表指定的时间
    • Date date = new Date(this.scheduledExecutionTime());
  • Calendar 日历类
    • 从JDK1.1版本开始,在处理日期和时间时,系统推荐使用Calendar类进行实现。
    • Calender.HOUR 12小时制
    • Calender.HOUR_OF_DAY 24小时制
  • 注意: 如果延迟执行时间在当前时间之前,则立即执行
3. 在延迟指定时间后以指定的间隔时间循环执行定时任务
public class TestUserTimer {
    public static void main(String[] args) {
        System.out.println("任务开始");
        timer();
    }

//    在延迟指定时间后以指定的间隔时间循环执行定时任务
    private static void timer(){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行一次该任务: " + new Date());
            }
        },2000,1000);
    }
}

输出结果:

任务开始
执行一次该任务: Thu May 31 15:35:49 CST 2018
执行一次该任务: Thu May 31 15:35:50 CST 2018
执行一次该任务: Thu May 31 15:35:51 CST 2018
执行一次该任务: Thu May 31 15:35:52 CST 2018
执行一次该任务: Thu May 31 15:35:53 CST 2018

4、Timer类小结

Timer类是一种简单实用的实现定时任务的方法,然而它存在着自身的缺陷:

(1)Timer对调度的支持是基于绝对时间而不是相对时间,因此它对于系统时间的改变非常敏感

(2)Timer线程是不会捕获异常的,如果TimerTask抛出未检查的异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,它会错误的认为整个Timer线程都会取消,已经被安排但尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。因此,如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。

二、ScheduledExecutorService

​ ScheduledExecutorService是基于相对时间Timer内部是单一线程

ScheduledThreadPoolExecutor内部是个线程池,可以支持多个任务并发执行

ScheduledExecutor的设计思想是每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发的,相互之间不会受到干扰;只有当任务的时间到来时,ScheduledExecutor才会真正启动一个线程。

1、Timer的第一个缺陷

public class TestUserTimer {
    private Timer timer;
    private long  start;

    public TestUserTimer() {
        this.timer = new Timer();
        start = System.currentTimeMillis();
    }

    /**
     * 延迟一秒执行调度任务,输出后睡眠4 秒
     */
    public void timerOne(){
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务1开始执行: " + (System.currentTimeMillis()- start) +"毫秒");
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },1000);
    }

    public void timerTwo(){
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务2开始执行: " + (System.currentTimeMillis()-start) + "毫秒");
            }
        },3000);
    }
    public static void main(String[] args) {
        TestUserTimer testUserTimer = new TestUserTimer();
        System.out.println("任务开始: " + testUserTimer.start);
        testUserTimer.timerOne();
        testUserTimer.timerTwo();
    }
}

运行结果:

任务开始: 1527753568049
任务1开始执行: 1007毫秒
任务2开始执行: 5007毫秒

按照设想,任务1与开始时间间隔为1秒,而任务2与开始时间的时间间隔为3秒。然而,由于Timer在执行定时任务时只会创建一个工作线程,当工作线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,则会出现以上情况。

  • 上述代码说明Timer类是单线程的.

使用ScheduledExecutorService优化:

public class TestScheduledExecutorTimer {

    private ScheduledExecutorService  schedule;
    private long  start;

    public TestScheduledExecutorTimer() {
        this.schedule =  Executors.newScheduledThreadPool(2); //执行器创建预期的线程池
        start = System.currentTimeMillis();
    }


    public void timerOne(){
        schedule.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务开始执行,与开始时间的时间间隔为: " +(System.currentTimeMillis() - start) + "毫秒");
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },1000, TimeUnit.MILLISECONDS);
    }

    public void timerTwo(){
        schedule.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2开始执行: " + (System.currentTimeMillis()-start) + "毫秒");
            }
        },2000,TimeUnit.MILLISECONDS);
    }

    public static void main(String[] args) {
        TestScheduledExecutorTimer testScheduledExecutorTimer = new TestScheduledExecutorTimer();
        System.out.println("任务开始: " + testScheduledExecutorTimer.start);
        testScheduledExecutorTimer.timerOne();
        testScheduledExecutorTimer.timerTwo();
    }
}

输出结果:

任务开始: 1527760168681
任务开始执行,与开始时间的时间间隔为: 1004毫秒
任务2开始执行: 2005毫秒

  • 上述代码说明ScheduledExecutorService是多线程并行的

2.Timer的第二个缺陷

public class TestTimerDemo {

    private Timer timer;
    private long  start;

    public TestTimerDemo() {
        this.timer = new Timer();
        start = System.currentTimeMillis();
    }


    public void timerOne(){
         timer.schedule(new TimerTask() {
        @Override
            public void run() {
               throw  new RuntimeException();
            }
        },1000);
    }

    public void timerTwo(){
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务2开始执行: " + new Date());
            }
        },3000);
    }

    public static void main(String[] args) {
        TestTimerDemo timerDemo = new TestTimerDemo();
        System.out.println("任务开始: " + timerDemo.start);
        timerDemo.timerOne();
        timerDemo.timerTwo();
    }
}

输出结果:

任务开始: 1527761009205
Exception in thread “Timer-0” java.lang.RuntimeException
at com.sojson.test.TestTimerDemo$1.run(TestTimerDemo.java:45)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)

  • 上述代码表名
    • timerOne抛出异常,而timerTwo并没有执行。

使用ScheduledExecutorService优化:

public class TestScheduledExecutorTimer {

    private ScheduledExecutorService  schedule;
    private long  start;

    public TestScheduledExecutorTimer() {
        this.schedule =  Executors.newScheduledThreadPool(2); //执行器创建预期的线程池
        start = System.currentTimeMillis();
    }

    /**
     * 延迟一秒执行调度任务,输出后睡眠4 秒
     */
    public void timerOne(){
        schedule.schedule(new Runnable() {
            @Override
            public void run() {
                throw new RuntimeException();
            }
        },1000, TimeUnit.MILLISECONDS);
    }

    public void timerTwo(){
        schedule.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2开始执行: " + (System.currentTimeMillis()-start) + "毫秒");
            }
        },2000,TimeUnit.MILLISECONDS);
    }

    public static void main(String[] args) {
        TestScheduledExecutorTimer testScheduledExecutorTimer = new TestScheduledExecutorTimer();
        System.out.println("任务开始: " + testScheduledExecutorTimer.start);
        testScheduledExecutorTimer.timerOne();
        testScheduledExecutorTimer.timerTwo();
    }
}

输出结果:

任务开始: 1527761726364
任务2开始执行: 2025毫秒

  • 上述代码说明:ScheduledExcetorService是多线程的,线程之间互不影响.

3.ScheduledExecutorService小结

可以看到针对Timer类存在的两个缺陷,ScheduledExecutorService可以很好地解决,其思路主要在于每一个被调度的任务都是由线程池中的一个线程去执行,任务之间是并发的,不会互相干扰。

三、Quartz

​ Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单强大的机制。与前两种方法相比,Quartz对于定时的配置更为丰富,实际应用的场景多。

Quartz最重要的3个基本要素:

  • Scheduler——调度器,所有的调度都由它控制;
  • Trigger——定义触发的条件,包括SimpleTrigger和CronTrigger等;
  • JobDetail & Job——JobDetail定义的是任务数据,而真正的执行逻辑在Job中。Scheduler的每次执行都会根据JobDetail创建一个新的Job实例。
详情请看Quartz使用教程.