zoukankan      html  css  js  c++  java
  • Java定时任务-Timer

    1. Timer简单使用

    简单使用:

    public class TimerTest {
        public static void main(String[] args) throws InterruptedException {
            // 创建Timer对象
            Timer timer = new Timer();
            // 延迟1秒执行任务,只执行一次。
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            }, 1000);
            Thread.sleep(2000);
            timer.cancel(); // 取消任务执行,如果没有调用cancel,timer线程不会结束。
        }
    }
    

    TimerTask接口继承了Runnable,重写里面的run方法就可以自定义任务。

    2. Timer

    Timer类中,有两个重要字段:

    /**
    * The timer task queue.  This data structure is shared with the timer
    * thread.  The timer produces tasks, via its various schedule calls,
    * and the timer thread consumes, executing timer tasks as appropriate,
    * and removing them from the queue when they're obsolete.
    */
    private final TaskQueue queue = new TaskQueue();
    /**
    * The timer thread.
    */
    private final TimerThread thread = new TimerThread(queue);
    

    TaskQueue用来存储提交的定时任务的队列;TimerThread是Timer的内部类,继承了Thread类。

    TaskQueue的底层实现是数组,它是一个优先队列。优先级参考下一次执行时间(每一个TimerTask下一次的执行时间),越快该执行的任务就会排的越靠前。

    private TimerTask[] queue = new TimerTask[128];
    

    TimerThread是一个线程,当Timer创建时,该线程就被启动:

    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }
    

    3. 配置定时任务

    主要用Timer中的schedule方法来配置不同的定时任务。

    • schedule(TimerTask task, long delay):在delay(毫秒)延迟后,执行任务task。只执行一次。
    • schedule(TimerTask task, Date time):在指定的时间点(time)执行task。如果时间点是过去某个时刻,那么该任务将被立即执行。
    • schedule(TimerTask task, long delay, long period):在当前时间delay(毫秒)延迟后执行task,然后每隔period(毫秒)执行一次
    • schedule(TimerTask task, Date firstTime, long period):在指定时间firstTime执行第一次task,然后每隔period(毫秒)执行一次。
    • scheduleAtFixedRate(TimerTask task, long delay, long period):在delay延迟后执行task,每隔period执行一次
    • scheduleAtFixedRate(TimerTask task, Date firstTime, long period):在指定时间执行task,每隔period执行一次

    3.1 schedule与scheduleAtFixedRate的区别

    3.1.1 delay参数的方法

    schedule(TimerTask task, long delay, long period)scheduleAtFixedRate(TimerTask task, long delay, long period) 的区别:
    两者都是在当前时间的delay延迟后,执行第一次任务,并且之后每隔period执行一次。

    区别在于:当任务的执行时间 > 任务间间隔时间(period)时,

    • schedule方法会阻塞,直到上一个任务执行完毕后,再执行下一个任务;
    • scheduleAtFixedRate方法不会阻塞,上一个任务即使未执行完毕,只要间隔时间足够period,就执行下一个任务。

    在演示前,需要搞明白两个时间变量:

    • 系统时间(System.currentTimeMillis()或new Date()):正常流逝的时间。
    • 任务被执行的时间(TimerTask中的scheduledExecutionTime()方法):按照指定参数,任务应该在哪个时间点被执行。
      存在这一种情况:由于Timer是单线程执行任务,当任务的执行时间超过指定的任务间隔时间时,scheduleExecutionTime()方法的时间会早于系统时间

    示例:schedule

    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // 我们在任务中分别打印“任务被执行时间”和当前系统时间
                System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
                System.out.println("new Date() : " + dateFormat.format(new Date()));
                try {
                    // 阻塞6秒,超过了任务间隔4秒
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, 2000, 4000); // 指定任务执行间隔4秒
    }
    

    打印结果:

    scheuledExecutionTime : 2020-08-23 13:49:33
    new Date() : 2020-08-23 13:49:33
    scheuledExecutionTime : 2020-08-23 13:49:39
    new Date() : 2020-08-23 13:49:39
    scheuledExecutionTime : 2020-08-23 13:49:45
    new Date() : 2020-08-23 13:49:45
    scheuledExecutionTime : 2020-08-23 13:49:51
    new Date() : 2020-08-23 13:49:51
    scheuledExecutionTime : 2020-08-23 13:49:57
    new Date() : 2020-08-23 13:49:57
    

    可以看到,任务执行时间(scheduleExecutionTime)和系统时间的间隔都是6秒。
    任务执行的实际间隔是6秒,也就是上一个任务结束后,下一个任务才执行,并未按照设定的4秒间隔。 在schedule方法中,scheduleExecutionTime获取的是任务实际执行时的时间点。

    示例:scheduleAtFixedRate

     public static void main(String[] args) throws InterruptedException {
         Timer timer = new Timer();
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         timer.scheduleAtFixedRate(new TimerTask() {
             @Override
             public void run() {
                 System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
                 System.out.println("new Date() : " + dateFormat.format(new Date()));
                 try {
                     Thread.sleep(6000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }, 2000, 4000);
    }
    

    打印结果:

    scheuledExecutionTime : 2020-08-23 14:31:54
    new Date() : 2020-08-23 14:31:54
    scheuledExecutionTime : 2020-08-23 14:31:58
    new Date() : 2020-08-23 14:32:00
    scheuledExecutionTime : 2020-08-23 14:32:02
    new Date() : 2020-08-23 14:32:06
    scheuledExecutionTime : 2020-08-23 14:32:06
    new Date() : 2020-08-23 14:32:12
    scheuledExecutionTime : 2020-08-23 14:32:10
    new Date() : 2020-08-23 14:32:18
    

    可以看到:任务被执行的时间(scheduleExecutionTime)的间隔是4秒,而打印的系统时间间隔是6秒。也就是说,任务的执行时间间隔按照设定的4秒进行,并未因上一个任务的阻塞而导致间隔延长。
    但是注意:“scheuledExecutionTime”和“new Date() ”两句话都是同时打印的,都是在一个task开始时被打印。
    在每一次任务执行时,下一次任务的scheuledExecutionTime()都会被计算好,因此,在scheduleAtFixedRate方法中,scheduleExecutionTime()中获取的是按照指定时间间隔任务应该被执行的时间点 ,但任务实际被执行的时间明显晚于该时间点。

    3.1.2 firstTime参数的方法

    schedule(TimerTask task, Date firstTime, long period)scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 的区别:
    这两个方法的区别主要在于:
    当firstTime早于当前系统时间时,执行的情况不同。

    • schedule:会立即执行task一次,之后每隔period执行一次;
    • scheduleAtFixedRate:会将从firstTime到当前系统时间(System.currentTimeMillis())之间应该执行的任务一次性执行完毕,然后继续按照period间隔执行。

    示例:schedule

    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 当前时间
        System.out.printf("=============当前时间: %s=================
    ", dateFormat.format(new Date()));
        // 从当前时间的20秒前开始执行任务
        Date startDate = Date.from(Instant.now().minusSeconds(20));
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
            }
        }, startDate, 4000); // 每隔4秒执行一次
    }
    

    打印结果:

    =============当前时间: 2020-08-23 14:45:05=================
    scheuledExecutionTime : 2020-08-23 14:45:05
    scheuledExecutionTime : 2020-08-23 14:45:09
    scheuledExecutionTime : 2020-08-23 14:45:13
    scheuledExecutionTime : 2020-08-23 14:45:17
    scheuledExecutionTime : 2020-08-23 14:45:21
    

    可以看到,任务是从当前时间开始执行的,并未从startDate这个时间点开始。

    示例:scheduleAtFixedRate

    public static void main(String[] args) throws InterruptedException {
         Timer timer = new Timer();
         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         // 当前时间
         System.out.printf("=============当前时间: %s=================
    ", dateFormat.format(new Date()));
         // 从当前时间的20秒前开始执行任务
         Date startDate = Date.from(Instant.now().minusSeconds(20));
         timer.scheduleAtFixedRate(new TimerTask() {
             @Override
             public void run() {
                 System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
             }
         }, startDate, 4000);
     }
    

    打印结果:

    =============当前时间: 2020-08-23 14:47:30=================
    scheuledExecutionTime : 2020-08-23 14:47:10
    scheuledExecutionTime : 2020-08-23 14:47:14
    scheuledExecutionTime : 2020-08-23 14:47:18
    scheuledExecutionTime : 2020-08-23 14:47:22
    scheuledExecutionTime : 2020-08-23 14:47:26
    scheuledExecutionTime : 2020-08-23 14:47:30
    scheuledExecutionTime : 2020-08-23 14:47:34
    scheuledExecutionTime : 2020-08-23 14:47:38
    scheuledExecutionTime : 2020-08-23 14:47:42
    

    可以看到:scheduleAtFixedRate方法将从开始时间(startDate 14:47:10)到当前时间之间(new Date() 14:47:30)之间的所有任务都执行一遍(这些任务都是同时执行,一次性输出的),然后从当前时间起继续以4秒为间隔往下执行。

    4. 底层实现

    每一个schedule方法的底层都是调用了sched方法。

    private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");
        // Constrain value of period sufficiently to prevent numeric
        // overflow while still being effectively infinitely large.
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;
        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");
            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                    "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }
    

    该方法接收3个参数:

    • task:执行的任务;
    • time:任务下一次要执行的时间点;
    • period:执行任务的时间间隔。

    当我们构造 Timer 实例的时候,就会启动该线程,该线程会在一个死循环中尝试从任务队列上获取任务,如果成功获取就执行该任务并在执行结束之后做一个判断。

    如果 period 值为零,则说明这是一次普通任务,执行结束后将从队列首部移除该任务。

    如果 period 为负值,则说明这是一次固定延时的任务,修改它下次执行时间 nextExecutionTime 为当前时间减去 period,重构任务队列。

    如果 period 为正数,则说明这是一次固定频率的任务,修改它下次执行时间为 上次执行时间加上 period,并重构任务队列。

    5. Timer的不足

    Timer是单线程的,不管任务多少,仅有一个工作线程;
    限于单线程,如果第一个任务逻辑上死循环了,后续的任务一个都得不到执行。
    依然是由于单线程,任一任务抛出异常后,整个 Timer 就会结束,后续任务全部都无法执行。

    参考博客:https://www.cnblogs.com/yangming1996/p/10317949.html

  • 相关阅读:
    解决undefined reference to `__poll_chk@GLIBC_2.16' 错误
    交叉编译总结 libosscore.a libcurl.a libmysqlclient.a
    APUE环境配置
    UDT中epoll对CLOSE状态的处理
    查看ld搜索路径
    linux shell 比较文件夹内容 diff
    交互式makefile
    linux shell取文本最后一行
    linux 查看静态库,动态库是32位还是64位
    python学习day4之路
  • 原文地址:https://www.cnblogs.com/guyexiangyun/p/13549248.html
Copyright © 2011-2022 走看看