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

java Timer定时任务调度原理 java Timer定时任务调度原理记综述类关系图总体时序图模块详解TimerTimerThreadTaskQueue总结 记 最近看了一下java定时调度Timer的源码,源码简单、清晰,结构清除,对于了解任务调度入门有帮助,因此在此对源码阅读总结一下。 综述 java 的Timer是一个定时任务调度控制器, 在java 并行库中的ScheduledExecutorService面世前,java... 阅读详情

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

640?wx_fmt=gif

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);
}
640?wx_fmt=gif
堆排序

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

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

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);

任务锁

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();
  }
}

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();  // 唤醒轮训线程
}

Timer 终止

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

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

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

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

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();
            }
        }
  };
  ...
} 

640?wx_fmt=png

阅读更多精品文章,长按二维码识别关注公众号「码洞」

Diffix3D+复现 上下载模型,将其放在一个目录中,并将目录名填在demo代码中from_pretrained的cache_dir参数里。目录结构应该是"<PATH>/nvidia/difix/…给的requirements文件中pytorch和xformers的版本没有指定,这里测试过以下版本,可以成功运行。将readme中的demo代码新建一个python脚本复制进去,然后将脚本放在src目录中运行。如果遇到网络问题无法加载huggingface上的预训练模型,可以手动从。如果出现,以下错误重新。 阅读详情

相关推荐

PaddleOCR GPU部署:从环境配置到避坑实战指南

本文详细介绍了PaddleOCR GPU部署的全过程,从环境配置到避坑实战指南。通过分析常见的部署失败原因,提供硬件兼容性自查、软件环境精配方案以及路径配置的解决方案,帮助用户高效完成GPU加速的OCR任务部署,避免90%的常见错误。

weixin_30839881的博客 419

java 定时器实现原理(时间轮算法)

本文介绍了 Java 时间轮算法的原理、实现步骤以及相应的代码示例。时间轮算法是一种高效的定时器实现方式,通过将时间轮分成多个刻度,每个刻度代表一个时间段,来实现对大量定时任务的管理和触发。Java 时间轮算法具有高效、可扩展等优点,可以广泛应用于 NIO 网络编程、调度任务等场合。

兴趣是最好的老师,勤能补拙 3471

人工势场法在自动驾驶避障中的实践与优化

本文深入探讨了人工势场法在自动驾驶避障中的核心原理与实践优化。通过解析引力与斥力模型,揭示了经典算法在局部最优和目标不可达等问题上的局限性,并重点介绍了改进斥力场函数、引入道路边界斥力及动态参数调整等关键优化策略,有效提升了该局部路径规划算法在复杂动态场景下的实用性与鲁棒性。

weixin_29208327的博客 927

详解java定时任务

在我们编程过程中如果需要执行一些简单的定时任务,无须做复杂的控制,我们可以考虑使用JDK中的Timer定时任务来实现。下面LZ就其原理、实例以及Timer缺陷三个方面来解析java Timer定时器。一、简介      在java中一个完整定时任务需要由Timer、TimerTask两个类来配合完成。 API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任

chenssy 的技术博客 8万+

手把手教你用smarteye搭建免费GB28181监控平台(含海康设备接入避坑指南)

本文提供了一份基于SmartEye搭建免费GB28181视频监控平台的实战指南。详细介绍了从服务器部署、GB28181协议核心配置,到海康威视等主流设备接入的完整流程,并重点分享了设备注册失败、无法预览等常见问题的深度排错方法,旨在帮助中小企业及开发者低成本实现多品牌监控设备的统一管理。

pz89012345的博客 327

GEE开发之ERA5(气温、降水、压力、风速等)数据获取和分析

前言:ERA5主要提供DAILY和MONTHLY两大类别数据,其中主要包括气温、降水、压力、风速等。

等待着冬天的风的博客 3万+

Java-定时任务

在Java中,定时任务是指在预定的时间间隔或者指定的时间点自动执行的任务。定时任务通常用于执行一些周期性的操作,比如定期备份数据、定时清理缓存、定时发送邮件等。

架构师之路。。 1179

【低空经济】低空无人机公共测试场与验证基地设计方案

公共测试场与验证基地是为低空无人机相关技术的研发、测试与应用提供一个系统性的平台。在无人机产业蓬勃发展的背景下,建设一个配备完善、功能齐全的公共测试场与验证基地显得尤为重要。该基地不仅可以作为无人机的测试和验证场地,还可以为相关企业、科研机构以及政府部门提供技术支持和服务。因此,为此基地的设计方案必须综合考虑功能性、安全性、灵活性与可持续性等多方面的因素。首先,公共测试场需划分为多个专用区域,以便满足不同类型无人机的测试需求。这包括低空飞行区域、紧急着陆区、操控模拟区和监测与数据采集区等。

2502_92579126的博客 1603

Java 定时任务实现原理详解

在jdk自带的库中,有两种技术可以实现定时任务。一种是使用Timer,另外一个则是ScheduledThreadPoolExecutor。下面为大家分析一下这两个技术的底层实现原理以及各自的优缺点。 一、Timer 1. Timer的使用 class MyTask extends TimerTask{ @Override public void run() { ...

u013332124的专栏 5万+

Unity Android构建报错:compileSdk 35兼容性五层定位法

Android构建中的compileSdk是编译时API目标版本,其升级并非孤立操作,而是触发JDK、Android Gradle Plugin(AGP)、NDK、targetSdk与Unity引擎版本之间强耦合校验的起点。当出现'using a newer Android Gradle plugin to use compileSdk = 35'类报错,本质是代际技术栈错配——例如AGP 7.1.3不识别compileSdk 35所需Java 17语法,或NDK 21与AGP 7.4的ABI规范冲突。该问

weixin_32496547的博客 569

【RKNN】RKNN(Rockchip Neural Network)​​ 的详解

RKNN 为 Rockchip 硬件提供了高效的 AI 推理解决方案,适合需要低功耗、实时响应的边缘场景

浩瀚之水的专栏 1688

汇川PLC做Modbus RTU主站:现场调试避坑与浮点数高低位转换

Modbus RTU作为工业现场最通用的串行通信协议,凭借简单、稳定、低成本的优势,在PLC、仪表和变频器之间仍占据不可替代的位置。然而实际调试中,从RS485的A/B极性、终端电阻,到波特率校验位等参数配置,再到寄存器地址偏移和32位浮点数的高低字序问题,任何一个细节出错都可能导致通信失败或数据异常。尤其是汇川PLC等国产控制器作为主站时,读取第三方仪表的32位数据常常面临字节序和字序不匹配的困惑。本文基于真实工程案例,系统梳理了Modbus RTU现场调试的典型故障现象与排查思路,并给出了从物理层到应用

weixin_33981932的博客 391

8.26华为OD机试真题 新系统 - 仓库查询 (Java/Py/C/C++/Js/Go)

8.26华为OD机试真题 新系统 - 仓库查询 (Java/Py/C/C++/Js/Go)

算法大师! 1743

Timer与TimerTask

Timer是Java中的定时任务调度器,而TimerTask相当于一个定时需要被调度的任务。

u013281331的专栏 516

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Java线程池架构2-多线程调度器

http://ifeve.com/java线程池架构2-多线程调度器(scheduledthreadpoolexecutor)/ 在前面介绍了java的多线程的基本原理信息:《Java线程池架构原理和源码解析(ThreadPoolExecutor)》,本文对这个java本身的线程池的调度器做一个简单扩展,如果还没读过上一篇文章,建议读一下,因为这是调度器的核心组件部分。  我们如果

novelly的专栏 813

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

java作业调度、定时任务实践(三种实现方式)

转载自 http://wade6.iteye.com/blog/1876290

Anchor_0的博客 5736

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Spring,jdk定时任务的几种实现以及任务线程是串行还是并行执行(转载)

近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将结合 spring框架来介绍。 一.分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerT...

正走向大牛的菜鸟 522

spring 定时任务

以前有用过 quartz

逍遥法外 995

Spring定时任务的几种实现

近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息,借此机会整理了一下定时任务的几种实现方式,由于项目采用spring框架,所以我都将结合 spring框架来介绍。 一.分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): Java自带的java.util.Timer类,这个类允许你调度一个java.util.Timer

zqh3239540的专栏 346

spring4.2 定时任务

一.实现定时任务的方式主要有三种 1.通过JDK自带的类实现,即:java.util.Timer结合java.util.TimerTask,使用这种方式可以让你的程序按照某一个频度执行,但调度控制非常不方便,需要大量代码,不推荐 2.使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂,推荐获取【...

weixin_33851429的博客 102

Spring task quartz 定时任务的几种实现

一.分类 从实现的技术上来分类,目前主要有三种技术(或者说有三种产品): Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少,这篇文章将不做详细介绍。使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按

WEILIN19921214的专栏 673

Java Timer 定时调度器实现原理

Java Timer 定时调度器实现原理 # 定时调度器 使用 Java 来调度定时任务时,我们经常会使用 Timer 类搞定。Timer 简单易用,其源码阅读起来也非常清晰,本文中我们来仔细分析一下 Timer 类,来看看 JDK 源码的编写者是如何实现一个稳定可靠的简单调度器。 Timer 使用Timer 调度任务有一次性调度和循环调度,循环调度有分为固定速率调度(fixRate)和固定时延调度(fixDelay)。固定速率就好比你今天加班到很晚,但是到了第二天还必须准点到公司上班,如果你一.

weixin_42408930的博客 543
上一篇: 深入理解「分布式事务」
下一篇: 为什么 Go 语言能在中国这么火?
码洞
博客等级 码龄10年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值