Java中JDK定时任务

  • 一、JDK自带Timer
  • 1.Timer核心方法
  • (1)schedule与scheduleAtFixedRate区别
  • schedule侧重保持间隔时间的稳定
  • scheduleAtFixedRate保持执行频率的稳定
  • 2.java.util.TimerTask
  • (1)TimerTask核心方法
  • 3.Timer的缺陷
  • 4.Timer使用示例
  • (1)指定延迟执行一次
  • (2)固定间隔执行
  • (3)固定速率执行
  • 二、JDK自带ScheduledExecutorService
  • 1.ScheduledExecutorService核心方法
  • 2.ScheduledExecutorService使用示例
  • (1)scheduleWithFixedDelay
  • (2)scheduleAtFixedRate



一、JDK自带Timer

  • 参考官方文档 ===> Timer - Java 11 中文版 - API 参考文档
  • java.util.Timer 线程的工具,用于在后台线程中安排将来执行的任务。 可以将任务安排为一次性执行,或者以固定间隔重复执行
  • 对应于每个Timer对象的是一个后台线程,用于按顺序执行所有计时器的任务。 计时器任务应该快速完成。 如果计时器任务需要花费过多的时间来完成,它会“占用”计时器的任务执行线程。 反过来,这可以延迟后续任务的执行,后续任务可以在紧急任务最终完成时(以及如果)快速连续地“聚集”并执行。
  • 在对Timer对象的最后一次实时引用消失并且所有未完成的任务都已完成执行之后,计时器的任务执行线程正常终止(并且变为垃圾回收)。 但是,这可能需要很长时间才能发生。默认情况下,任务执行线程不作为守护程序线程运行,因此它能够阻止应用程序终止。如果调用者想要快速终止计时器的任务执行线程,则调用者应该调用计时器的cancel方法。
  • 此类是线程安全的:多个线程可以共享单个Timer对象,而无需外部同步。

1.Timer核心方法

  • java.util.Timer 类的核心方法如下:
void cancel()	//终止此计时器,丢弃当前计划的任何任务。
int	purge()	//从此计时器的任务队列中删除所有已取消的任务。
void schedule(TimerTask task, long delay)	//在指定的延迟后安排指定的任务执行。
void schedule(TimerTask task, long delay, long period)	//在指定的延迟之后开始,为重复的固定延迟执行安排指定的任务。
void schedule(TimerTask task, Date time)	//计划在指定时间执行指定的任务。
void schedule(TimerTask task, Date firstTime, long period)	//从指定时间开始,为重复的固定延迟执行安排指定的任务。
void scheduleAtFixedRate(TimerTask task, long delay, long period)	//在指定的延迟之后开始,为重复的固定速率执行安排指定的任务。
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)  //从指定时间开始,为重复的 固定速率执行安排指定的任务。

(1)schedule与scheduleAtFixedRate区别

  • 在了解schedulescheduleAtFixedRate方法的区别之前,先看看它们的相同点:
  • 任务执行未超时,下次执行时间 = 上次执行开始时间 + period;
  • 任务执行超时,下次执行时间 = 上次执行结束时间;
  • 在任务执行未超时时,它们都是上次执行时间加上间隔时间,来执行下一次任务。而执行超时时,都是立马执行。
  • 它们的不同点在于侧重点不同,schedule方法侧重保持间隔时间的稳定,而scheduleAtFixedRate方法更加侧重于保持执行频率的稳定
schedule侧重保持间隔时间的稳定
  • schedule方法会因为前一个任务的延迟而导致其后面的定时任务延时。计算公式为scheduledExecutionTime(第n+1次) = realExecutionTime(第n次) + periodTime。
  • 也就是说如果第n次执行task时,由于某种原因这次执行时间过长,执行完后的systemCurrentTime>= scheduledExecutionTime(第n+1次)则此时不做时隔等待,立即执行第n+1次task
  • 而接下来的第n+2次task的scheduledExecutionTime(第n+2次)就随着变成了realExecutionTime(第n+1次)+periodTime。这个方法更注重保持间隔时间的稳定。
scheduleAtFixedRate保持执行频率的稳定
  • scheduleAtFixedRate在反复执行一个task的计划时,每一次执行这个task的计划执行时间在最初就被定下来了,也就是scheduledExecutionTime(第n次)=firstExecuteTime +n*periodTime
  • 如果第n次执行task时,由于某种原因这次执行时间过长,执行完后的systemCurrentTime>= scheduledExecutionTime(第n+1次),则此时不做period间隔等待,立即执行第n+1次task。
  • 接下来的第n+2次的task的scheduledExecutionTime(第n+2次)依然还是firstExecuteTime+(n+2)*periodTime这在第一次执行task就定下来了。说白了,这个方法更注重保持执行频率的稳定。

如果用一句话来描述任务执行超时之后schedule和scheduleAtFixedRate的区别就是:schedule的策略是错过了就错过了,后续按照新的节奏来走;scheduleAtFixedRate的策略是如果错过了,就努力追上原来的节奏(制定好的节奏)。

2.java.util.TimerTask

  • 参考官方文档 === > TimerTask - Java 11 中文版 - API 参考文档
  • java.util.TimerTask 可由java.util.Timer一次性或重复执行的任务 。
  • 计时器任务不可重复使用。 一旦任务被安排在Timer上执行或被取消,后续尝试安排执行将抛出IllegalStateException
package java.util;

public abstract class TimerTask implements Runnable {
    /**
     * This object is used to control access to the TimerTask internals.
     */
    final Object lock = new Object();

    /**
     * The state of this task, chosen from the constants below.
     */
    int state = VIRGIN;

    /**
     * This task has not yet been scheduled.
     */
    static final int VIRGIN = 0;

    /**
     * This task is scheduled for execution.  If it is a non-repeating task,
     * it has not yet been executed.
     */
    static final int SCHEDULED   = 1;

    /**
     * This non-repeating task has already executed (or is currently
     * executing) and has not been cancelled.
     */
    static final int EXECUTED    = 2;

    /**
     * This task has been cancelled (with a call to TimerTask.cancel).
     */
    static final int CANCELLED   = 3;
    long nextExecutionTime;
    long period = 0;

    protected TimerTask() {
    }

    public abstract void run();

    public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }
    
    public long scheduledExecutionTime() {
        synchronized(lock) {
            return (period < 0 ? nextExecutionTime + period
                               : nextExecutionTime - period);
        }
    }
}

(1)TimerTask核心方法

  • java.util.TimerTask 类的核心方法如下:
boolean	cancel()	//取消此计时器任务。
abstract void run()	//此计时器任务要执行的操作。
long scheduledExecutionTime()	//返回此任务最近 实际执行的 计划执行时间。

3.Timer的缺陷

  • Timer计时器可以定时(指定时间执行任务)、延迟(延迟5秒执行任务)、周期性地执行任务(每隔个1秒执行任务)。但是,Timer存在一些缺陷。
  • 首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。
  • 其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,它会错误的认为整个Timer线程都会取消。同时,已经被安排但尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。由于 Timer 底层是单线程

4.Timer使用示例

(1)指定延迟执行一次

  • 在指定延迟时间后执行一次,这类是比较常见的场景,比如:当系统初始化某个组件之后,延迟几秒中,然后进行定时任务的执行。
public class TestTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();

        //定时任务启动
        System.out.println(new Date() + "定时任务开始");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000L);//模拟执行任务花费的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + "---任务执行完成");
            }
        }, 2000L);
    }
}

(2)固定间隔执行

  • 在指定的时间开始执行定时任务,定时任务按照固定的间隔进行执行。比如:程序开始运行就执行,固定执行间隔为2秒。
public class TestTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTaskImpl(),new Date(), 2000L);
    }
}
class TimerTaskImpl extends TimerTask {

    private boolean test = true;

    @Override
    public void run() {
        System.out.println(new Date() + "---定时任务开始");
        if(test) {
            try {
                Thread.sleep(3000L);//模拟执行任务花费的时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new Date() + "---任务执行完成");
            test = !test;
        }
        else {
            try {
                Thread.sleep(1000L);//模拟执行任务花费的时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new Date() + "---任务执行完成");
        }
    }
}

java定时任务放到配置文件里 java自带的定时任务_System

(3)固定速率执行

  • 在指定的时间开始执行定时任务,定时任务按照固定的速率进行执行。比如:程序开始运行就执行,固定执行速率为2秒。
public class TestTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTaskImpl(),new Date(), 2000L);
    }
}
class TimerTaskImpl extends TimerTask {

    private boolean test = true;

    @Override
    public void run() {
        System.out.println(new Date() + "---定时任务开始");
        if(test) {
            try {
                Thread.sleep(3000L);//模拟执行任务花费的时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new Date() + "---任务执行完成");
            test = !test;
        }
        else {
            try {
                Thread.sleep(1000L);//模拟执行任务花费的时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(new Date() + "---任务执行完成");
        }
    }
}

java定时任务放到配置文件里 java自带的定时任务_System_02


二、JDK自带ScheduledExecutorService

  • ScheduledExecutorService - Java 11中文版 - API参考文档
  • ScheduledExecutorService 解决了 Timer 的缺陷问题。
  • ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行。也就是说,任务是并发执行,互不影响。
  • 需要注意:只有当执行调度任务时,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是处于轮询任务的状态。

1.ScheduledExecutorService核心方法

  • ScheduledExecutorService主要有以下4个方法:
// 提交在给定延迟后启用的一次性任务。
ScheduledFuture<?>	schedule(Runnable command,
 							 long delay,
 							 TimeUnit unit)	
//提交一个返回值的一次性任务,该任务在给定的延迟后变为启用状态。
<V> ScheduledFuture<V>	schedule(Callable<V> callable,
								 long delay,
								 TimeUnit unit)	
//提交定期操作,该操作在给定的初始延迟后首先启用,随后在给定的时间段内启用; 也就是说,执行将在initialDelay之后开始,然后是initialDelay + period ,然后是initialDelay + 2 * period ,依此类推。
ScheduledFuture<?>	scheduleAtFixedRate(Runnable command, //command为被执行的线程
										long initialDelay, //initialDelay为初始化后延时执行时间
										long period, //period为两次开始执行最小间隔时间
										TimeUnit unit) //unit为计时单位
//提交在给定的初始延迟之后首先启用的定期动作,并且随后在一次执行的终止和下一次执行的开始之间给定延迟。
ScheduledFuture<?>	scheduleWithFixedDelay(Runnable command, //command为被执行的线程
										   long initialDelay, //initialDelay为初始化后延时执行时间
										   long delay, //delay为前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
										   TimeUnit unit) //unit为计时单位。
  • ScheduledExecutorService中定义的这四个接口方法和Timer中对应的方法几乎一样,只不过Timerscheduled方法需要在外部传入一个TimerTask的抽象任务。而ScheduledExecutorService封装的更加细致了,传RunnableCallable内部都会做一层封装,封装一个类似TimerTask的抽象任务类(ScheduledFutureTask)。然后传入线程池,启动线程去执行该任务。

2.ScheduledExecutorService使用示例

(1)scheduleWithFixedDelay

  • scheduleWithFixedDelay是不管任务执行多久,都会等上一次任务执行完毕后再延迟delay后去执行下次任务。
public class ScheduleWithFixedDelay implements Runnable{
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(15); //设置线程数
        executor.scheduleWithFixedDelay(
                new ScheduleWithFixedDelay(),
                0,
                2000,
                TimeUnit.MILLISECONDS);
    }

    @Override
    public void run() {
        System.out.println(new Date() + "  " + Thread.currentThread() +" : 任务「ScheduleWithFixedDelay」被执行。");
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

(2)scheduleAtFixedRate

  • scheduleAtFixedRate是以period为间隔来执行任务的,如果任务执行时间小于period,则上次任务执行完成后会间隔period后再去执行下一次任务;但如果任务执行时间大于period,则上次任务执行完毕后会不间隔立即开始下次任务
public class ScheduleAtFixedRate implements Runnable {
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(13);
        executor.scheduleAtFixedRate(
                new ScheduleAtFixedRate(),
                0,
                1000,
                TimeUnit.MILLISECONDS);
    }

    @Override
    public void run() {
        System.out.println(new Date() + " " + Thread.currentThread() + ": 任务「ScheduleAtFixedRate」被执行。");
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

java定时任务放到配置文件里 java自带的定时任务_java定时任务放到配置文件里_03