iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >怎么深入Java Timer 定时任务调度器实现原理
  • 202
分享到

怎么深入Java Timer 定时任务调度器实现原理

2023-06-02 19:06:52 202人浏览 八月长安
摘要

这篇文章将为大家详细讲解有关怎么深入Java Timer 定时任务调度器实现原理,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。使用 Java 来调度定时任务时,我们经常会使用 Timer 类

这篇文章将为大家详细讲解有关怎么深入Java Timer 定时任务调度器实现原理,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

使用 Java 来调度定时任务时,我们经常会使用 Timer 类搞定。Timer 简单易用,其源码阅读起来也非常清晰,本节我们来仔细分析一下 Timer 类,来看看 jdk 源码的编写者是如何实现一个稳定可靠的简单调度器。

Timer 使用

Timer 调度任务有一次性调度和循环调度,循环调度有分为固定速率调度(fixRate)和固定时延调度(fixDelay)。固定速率就好比你今天加班到很晚,但是到了第二天还必须准点到公司上班,如果你一不小心加班到了第二天早上 9 点,你就连休息的时间都没有了。而固定时延的意思是你必须睡够 8 个小时再过来上班,如果你加班到凌晨 6 点,那就可以下午过来上班了。固定速率强调准点,固定时延强调间隔。

Timer timer = new Timer();

TimerTask task = new TimerTask() {
  public void run() {
    System.out.println("wtf");
  }
};

// 延迟 1s 打印 wtf 一次
timer.schedule(task, 1000)
// 延迟 1s 固定时延每隔 1s 周期打印一次 wtf
timer.schedule(task, 1000, 1000);
// 延迟 1s 固定速率每隔 1s 周期打印一次 wtf
timer.scheduleAtFixRate(task, 1000, 1000)


如果你有一个任务必须每天准点调度,那就应该使用固定速率调度,并且要确保每个任务执行时间不要太长,千万别超过了第二天这个点。如果你有一个任务需要每隔几分钟跑一次,那就使用固定时延调度,它不是很在乎你的单个任务要跑多长时间。

内部结构

Timer 类里包含一个任务队列和一个异步轮训线程。任务队列里容纳了所有待执行的任务,所有的任务将会在这一个异步线程里执行,切记任务的执行代码不可以抛出异常,否则会导致 Timer 线程挂掉,所有的任务都没得执行了。单个任务也不易执行时间太长,否则会影响任务调度在时间上的精准性。比如你一个任务跑了太久,其它等着调度的任务就一直处于饥饿状态得不到调度。所有任务的执行都是这单一的 TimerThread 线程。

class Timer {
  TaskQueue queue = new TaskQueue();
  TimerThread thread = new TimerThread(queue);
}
怎么深入Java Timer 定时任务调度器实现原理


Timer 的任务队列 TaskQueue 是一个特殊的队列,它内部是一个数组。这个数组会按照待执行时间进行堆排序,堆顶元素总是待执行时间最小的任务。轮训线程会每次轮训出时间点最近的并且到点的任务来执行。数组会自动扩容,如果任务非常多。

class TaskQueue {
  TimerTask[] queue = new TimerTask[128];
  int size;
}


任意线程都可以通过 Timer.schedule 方法将任务加入 TaskQueue,但是 TaskQueue 又并不是线程安全数据结构。所在每次修改 TaskQueue 时都需要加

synchronized(queue) {
  ...
}

任务状态

TimerTask 有 4 个状态,VIRGIN 是默认状态,刚刚实例化还没有被调度。SCHEDULED 表示已经将任务塞进 TaskQueue 等待被执行。EXECUTED 表示任务已经执行完成。CANCELLED 表示任务被取消了,还没来得及执行就被人为取消了。

abstract class TimerTask {
  int state = VIRGIN;
  static final int VIRGIN = 0;
  static final int SCHEDULED = 1;
  static final int EXECUTED = 2;
  static final int CANCELLED = 3;

  long nextExecutionTime; // 下次执行时间
  long period = 0; // 间隔
}

对于一个循环任务来说,它不存在 EXECUTED 状态,因为它每次刚刚执行完成,就被重新调度了。EXECUTED 状态仅仅存在于一次性任务,而且这个状态其实并不是表示任务已经执行完成,它是指已经从任务队列里摘出来了,马上就要执行。

任务间隔字段 period 比较特殊,当使用固定速率时,period 为正值,当使用固定间隔时,period 为负值,当任务是一次性时,period 为零。下面是循环任务的下次调度时间设定

currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
// 固定时延基于 currentTime 顺延
// 固定速率基于 executionTime(设定时间) 顺延
// next_exec_time = exec_time + period = first_delay + n * period
queue.rescheduleMin(
      task.period<0 ? currentTime   - task.period  
                    : executionTime + task.period);


对于固定速率来说,如果任务执行时间太长超出了间隔,那么它可能会持续霸占任务队列,因为它的调度时间将总是低于 currentTime,排在堆顶,每次轮训取出来的都是它。运行完毕后,重新调度这个任务,它的时间依旧赶不上。持续下去你会看到这个任务的调度时间远远落后于当前时间,而其它任务可能会彻底饿死。这就是为什么一定要特别注意固定速率的循环任务运行时间不宜过长。

任务锁

Timer 的任务支持取消操作,取消任务的线程和执行任务的线程极有可能不是一个线程。有可能任务正在执行中,结果另一个线程表示要取消任务。这时候 Timer 是如何处理的呢?在 TimerTask 类里看到了一把锁。当任务属性需要修改的时候,都会加锁。

abstract class TimerTask {
  final Object lock = new Object();
}

// 取消任务
public boolean cancel() {
    synchronized(lock) {
        boolean result = (state == SCHEDULED);
        state = CANCELLED;
        return result;
    }
}

// 调度任务
private void sched(TimerTask task, long time, long period) {
  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;
 }
}

// 运行任务
private void mainLoop() {
  while(true) {
    synchronized(task.lock) {
        if (task.state == TimerTask.CANCELLED) {
            queue.removeMin();
            continue;
        }
        ...
        if(task.period == 0) {
          task.state = TimerTask.EXECUTED;
        } 
        ...
    }
    task.run();
  }
}


在任务运行之前会检查任务是不是已经被取消了,如果取消了,就从队列中移除。一旦任务开始运行 run(),对于单次任务来说它就无法被取消了,而循环任务将不会继续下次调度。如果任务没有机会得到执行(时间设置的太长),那么即使这个任务被取消了,它也会一直持续躺在任务队列中。设想如果你调度了一系列久远的任务,然后都取消了,这可能会成为一个内存泄露点。所以 Timer 还单独提供了一个 purge() 方法可以一次性清空所有的已取消的任务。

public int purge() {
    int result = 0;
    // 灭掉 CANCELLED 状态的任务
    synchronized(queue) {
        for (int i = queue.size(); i > 0; i--) {
             if (queue.get(i).state == TimerTask.CANCELLED) {
                queue.quickRemove(i);
                result++;
             }
         }
    }
    // 堆调整
    if (result != 0)
         queue.heapify();
    }
    return result;
}

任务队列空了

任务队列里没有任务了,调度线程必须按一定的策略进行睡眠。它需要睡眠一直到最先执行的任务到点时立即醒来,所以睡眠截止时间就是第一个任务将要执行的时间。同时在睡觉的时候,有可能会有新的任务被添加进来,它的调度时间可能会更加提前,所以当有新的任务到来时需要可以唤醒正在睡眠的线程。

private void mainLoop() {
  while(true) {
    ...
    task = queue.getMin();
    currentTime = System.currentTimeMillis();
    executionTime = task.nextExecutionTime;
    if(executionTime > currentTime) {
      // 开始睡大觉
      queue.wait(executionTime - currentTime);
    }
    ...
  }
}

// 新任务进来了
private void sched(TimerTask task, long time, long period) {
   ...
   queue.add(task);
   if (queue.getMin() == task)
        queue.notify();  // 唤醒轮训线程
}


代码中的 wait() 方法就是调用了 Object.wait() 来进行睡眠。当有新任务进来了,发现这个新任务的运行时间是最早的,那就调用 notify() 方法唤醒轮训线程。

Timer 终止

Timer 提供了 cancel() 方法清空队列,停止调度器,不允许有任何新任务进来。它会将 newTasksMayBeScheduled 字段设置为 false 表示 Timer 即将终止。

class TimerThread {
  ...
  boolean newTasksMayBeScheduled;  // 终止的标志
  ...
}

public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();
    }
}


如果 Timer 终止了,还有新任务进来就会抛出异常。

private void sched(TimerTask task, long time, long period) {
  synchronized(queue) {
    if (!thread.newTasksMayBeScheduled)
       throw new IllegalStateException("Timer already cancelled.");
    ...
  }
}


我们还注意到 Timer.cancel() 方法会唤醒轮训线程,为的是可以立即停止轮训。不过如果任务正在执行中,这之后 cancel() 就必须等到任务执行完毕才可以停止。

private void mainLoop() {
   while(true) {
      // 正常清空下,队列空了,轮训线程会休眠
      // 但是如果 newTasksMayBeScheduled 为 false
      // 那么循环会退出,轮训线程会终止
      while (queue.isEmpty() && newTasksMayBeScheduled)
          queue.wait();
      if (queue.isEmpty())
          break;
      ...
   }
}

垃圾回收

还有一个特殊的场景需要特别注意,那就是当轮训线程因为队列里没有任务而睡眠的时候,Timer 对象因为不再被引用而被垃圾回收了。这时候需要主动唤醒轮训线程,让它退出。

class Timer {
  ...
  private final Object threadReaper = new Object() {
        @SuppressWarnings("deprecation")
        protected void finalize() throws Throwable {
            synchronized(queue) {
                thread.newTasksMayBeScheduled = false;
                queue.notify();
            }
        }
  };
  ...


当 Timer 被回收时,内部字段 threadPeaper 指向的对象也会被回收。所以 finalize 方法将会被调用,唤醒并终止 Timer 轮训线程。如果没有这个 threadPeaper 对象就可能会导致 JVM 里留下僵尸线程。

关于怎么深入Java Timer 定时任务调度器实现原理就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

--结束END--

本文标题: 怎么深入Java Timer 定时任务调度器实现原理

本文链接: https://www.lsjlt.com/news/231088.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 怎么深入Java Timer 定时任务调度器实现原理
    这篇文章将为大家详细讲解有关怎么深入Java Timer 定时任务调度器实现原理,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。使用 Java 来调度定时任务时,我们经常会使用 Timer 类...
    99+
    2023-06-02
  • 深入了解Java定时器中的Timer的原理
    目录主要成员变量定时功能TimerThread结论Demo代码位置Java在1.3版本引入了Timer工具类,它是一个古老的定时器,搭配TimerTask和TaskQueue一起使用...
    99+
    2024-04-02
  • Java定时任务的实现原理是什么
    Java定时任务的实现原理是基于线程池和计时器的机制。在Java中,可以使用ScheduledExecutorService接口来创...
    99+
    2023-10-20
    Java
  • 实时操作系统:深入剖析实时任务的调度与管理
    一、实时任务的调度 实时任务是指具有严格时限要求的任务,必须在规定的时间内完成,否则会导致系统故障或不可预知的行为。实时任务的调度是RTOS的核心功能之一,其主要目标是确保所有实时任务都能按时完成。 1、调度算法 RTOS中常用的调度算...
    99+
    2024-02-03
    实时操作系统 调度算法 任务管理 优先级调度 时间片轮转调度 速率单调调度
  • GoLang中的timer定时器实现原理分析
    // NewTimer creates a new Timer that will send // the current time on its channel after at ...
    99+
    2023-02-02
    Go timer定时器 Go timer Go定时器
  • java任务调度quartz怎么实现
    使用Quartz实现Java任务调度的步骤如下:1. 导入Quartz库:在项目的依赖管理中,添加Quartz库的Maven或Gra...
    99+
    2023-09-18
    quartz java
  • Java实现Timer的定时调度函数schedule的四种用法
    目录schedule(task,time)schedule(task,time,period)schedule(task,delay)schedule(task, delay,per...
    99+
    2023-05-17
    Java Timer schedule Java schedule
  • 怎么理解Laravel定时任务调度机制
    本篇内容主要讲解“怎么理解Laravel定时任务调度机制”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解Laravel定时任务调度机制”吧!1. 基本实现逻辑一个复杂的web系统后台当中,...
    99+
    2023-06-29
  • Redis定时任务原理的实现
    目录数据结构常见操作1. 创建定时事件2. 触发定时事件3. 执行定时事件总结本文主要是基于 redis 6.2 源码进行分析定时事件的数据结构和常见操作。 数据结构 在 redis...
    99+
    2024-04-02
  • 如何实现基于Quartz定时调度任务
    小编给大家分享一下如何实现基于Quartz定时调度任务,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!简介Quarzt是一个项目中定时执行任务的开源项目,Quart...
    99+
    2023-05-30
    quartz
  • golang定时器Timer的用法和实现原理是什么
    本篇内容介绍了“golang定时器Timer的用法和实现原理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!TimerTimer是一种单...
    99+
    2023-07-06
  • golang定时器Timer的用法和实现原理解析
    目录一文搞懂golang定时器Timer的用法和实现原理前言Timertimer结构体创建定时器停止定时器重置定时器实现原理数据结构runtimeTimer创建Timer停止Time...
    99+
    2023-05-15
    golang定时器 golang定时器Ticker
  • Java Spring怎么实现定时任务
    本篇内容主要讲解“Java Spring怎么实现定时任务”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java Spring怎么实现定时任务”吧!java实现定时任务Jdk自...
    99+
    2023-07-02
  • java分布式任务调度怎么实现
    实现Java分布式任务调度可以使用以下方法:1. 使用Quartz:Quartz是一个功能强大的开源任务调度框架,可以在Java应用...
    99+
    2023-10-23
    java
  • 如何实现PHP底层的定时任务调度
    实现PHP底层的定时任务调度文章摘要:无论是网站还是后台任务,在很多场景下都需要定时执行某些特定的操作,如发送邮件、数据备份等。PHP作为一种流行的开发语言,本身并没有内置的定时任务调度功能,但我们可以借助一些工具或者编写代码来实现定时任务...
    99+
    2023-11-08
    PHP定时任务 调度器 底层操作
  • quartz任务调度怎么实现
    在Java中,可以使用Quartz任务调度框架来实现任务调度。以下是实现Quartz任务调度的一般步骤:1. 添加依赖:在项目的构建...
    99+
    2023-08-31
    quartz
  • springboot任务调度怎么实现
    要在Spring Boot中实现任务调度,可以使用Spring Task框架。下面是一些步骤来实现任务调度:1. 在pom.xml文...
    99+
    2023-10-11
    springboot
  • Java中怎么实现SpringBoot定时任务
    今天就跟大家聊聊有关Java中怎么实现SpringBoot定时任务,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。1. SpringBoot--任务:定时任务项目开发中经常需要执行一些...
    99+
    2023-06-20
  • 怎么在java中实现定时任务
    这篇文章给大家介绍怎么在java中实现定时任务,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一.分类从实现的技术上来分类,目前主要有三种技术(或者说有三种产品):Java自带的java.util.Timer类,这个类允...
    99+
    2023-06-15
  • SpringBoot定时任务调度与爬虫的配置实现
    SpringTask SpringTask是Spring自主研发的轻量级定时任务工具,相比于Quartz更加简单方便,且不需要引入其他依赖即可使用。 SpringTask的配置 在配...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作