首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【JAVA定时器】四种常见定时器的原理和简单实现

【JAVA定时器】四种常见定时器的原理和简单实现

作者头像
全栈程序员站长
发布于 2022-09-08 03:55:27
发布于 2022-09-08 03:55:27
6K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道 如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充

前言

定时器顾名思义,即定时触发某个事件,分离开来,即包含三个因素:定时触发某个事件,本文也将以此为基础介绍五种常见的定时器

本文只做基于SpringBoot的示例,其余版本的请自行查阅资料,大同小异

1.介绍

1.1.目的

定时器的目的即为了在某个时间点,程序自身主动触发某个事件,而不需要外力去开启或者启动,以节省人力并统一管理

1.2.示例场景

  • 管理系统,需要每日12点将前一天的数据进行备份,并生成历史数据统计
  • 宿管系统,每日10点将所有未归人员统计出来,主动交由管理人员
  • 硬件设备,需要每隔2分钟检查设备是否连接正常,设备异常需要更新状态到管理端,必要时通知有关人员
  • 图书馆借书管理系统,每天12点需要检查即将超时和已超时归还的书籍,并通过短信或其他途径通知有关人员
  • 手机下载管理系统,开启下载后每隔0.5s刷新一次下载进度,在下载完成或者长时间卡顿时告知用户
  • 订单管理系统,用户下达订单后开需要在半小时内付款,成功付款则生成订单结果,超时未付款则自动取消订单

是不是觉得很常见?

1.3.常见实现方案

  • @Scheduled注解:基于注解
  • Timer().schedule创建任务:基于封装类Timer
  • 线程:使用线程直接执行任务即可,可以与thread、线程池、ScheduleTask等配合使用
  • quartz配置定时器:基于springquartz框架

本文仅简述前3种,比较简单易懂,quartz会专门分离出来整理

2.@Scheduled注解

2.1.介绍:

使用注解标记需要定时执行的方法,并设置执行时间,便可使其在指定的时间执行指定方法

2.2.步骤:

  1. 使用注解@Scheduled标记目标方法,参数为执行时间
  2. 使用注解@EnableScheduling标记目标方法所在的类,或者直接标记项目启动类

2.3.注解:

  • 注解@Scheduled为方法注解,用于标记某个方法在何时定时执行
  • 需要配合另一个注解@EnableScheduling进行使用,该注解用于标记某个类,开启定时任务,通常标记在定时器所在的类,或者直接设置在项目启动类上

2.4.@Scheduled参数:

@Scheduled(fixedDelay = 5000):方法执行完成后等待5秒再次执行

@Scheduled(fixedRate = 5000):方法每隔5秒执行一次

@Scheduled(initialDelay=1000, fixedRate=5000):延迟1秒后执行第一次,之后每隔5秒执行一次

fixedDelayStringfixedRateStringinitialDelayString:与上诉三种作用一直,但参数为字符串类型,因而可以使用占位符,形如

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Scheduled(fixedDelayString = "${ 
     time.fixedDelay}")

@Scheduled(cron = "0 0,30 0,8 ? * ? "):方法在每天的8点30分0秒执行,参数为字符串类型,那么同理也可使用占位符,cron表达式请另行查阅资料,推荐看这篇文章:https://www.jianshu.com/p/1defb0f22ed1

2.5.示例

示例1:每隔3秒执行一次

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
@EnableScheduling
public class ScheduleTest { 
   

    private int count = 0;

    /** * 每3秒钟执行一次 */
    @Scheduled(cron = "*/3 * * * * ?")
    public void test1() { 
   
        System.out.println(count + ":" + (new Date()).toString());
        count++;
    }
}

示例2:第一次等待10秒,之后每3秒一次

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Component
@EnableScheduling
public class ScheduleTest { 
   

    private int count = 0;

    /** * 第一次等待10秒,之后每3秒钟执行一次 */
    @Scheduled(initialDelay = 10000, fixedRate = 3000)
    public void test1() { 
   
        System.out.println(count + ":" + (new Date()).toString());
        count++;
    }

}

2.6.小结

  • 优势:简单便捷,仅两行注解便完成了定时效果
  • 劣势:所有参数和执行的方法必须提前写入代码里,可扩展性极低

3.Timer().schedule创建任务

3.1.样例

使用非常简单,这里先给出样例,在对照进行介绍

代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.yezi_tool.demo_basic.test;

import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

@Component
public class TimerTest { 
   
    private Integer count = 0;

    public TimerTest() { 
   
        testTimer();
    }

    public void testTimer() { 
   
        new Timer().schedule(new TimerTask() { 
   
            @Override
            public void run() { 
   
                try { 
   
                    //do Something
                    System.out.println(new Date().toString() + ": " + count);
                    count++;
                } catch (Exception e) { 
   
                    e.printStackTrace();
                }
            }
        }, 0, 1000);
    }
}

执行结果

可以看到每隔1s打印一次count并自增1

3.2.介绍

核心包括Timer和TimerTask,均为jkd自带的工具类,代码量分别为721行和162行(包括注释),都不多,有兴趣的可以直接看看源码

3.2.1.TimerTask

TimerTask实际上就是一个Runnable而已,继承Runnable并添加了几个自定义的参数和方法,没啥好介绍的,有兴趣可以看源码

3.2.2.Timer

Timer字面意思即定时器,为jkd自带的工具类,提供定时执行任务的相关功能

实际上包括三个类:

  • Timer:即定时器主类,负责管理所有的定时任务,每个Timer拥有一个私有的TaskQueueTimerThread
  • TaskQueue:即任务队列,Timer生产任务,然后推到TaskQueue里存放,等待处理,被处理掉的任务即被移除掉 TaskQueue实质上只有一个长度为128的数组用于存储TimerTask、一个int型变量size表示队列长度、以及对这两个数据的增删改查
  • TimerThread:即定时器线程,线程会共享TaskQueue里面的数据,TimerThread会对TaskQueue里的任务进行消耗 TimerThread实际上就是一个Thread线程,会不停的监听TaskQueue,如果队列里面有任务,那么就执行第一个,并将其删除(先删除再执行)

流程分析

  • Timer生产任务(实际上是从外部接收到任务),并将任务推到TaskQueue里面存放,并唤醒TaskQueue线程(queue.notify()
  • TimerThread监听TaskQueue,若里面有任务则将其执行并移除队里,若没有任务则让队列等待(queue.wait()

这么一看,这不就是典型的==生产者/消费者模式==,timer负责生产(实际上是接受),而TimerThread负责消费,TaskQueue作为中转仓库

构造方法

构造的时候会设置定时器线程的名字并将其启动 完整格式如下,其中两个参数均可缺省 public Timer(String name, boolean isDaemon)

  • name:即线程名,用于区分不同的线程,缺省的时候默认使用"Timer-" + serialNumber()生成唯一线程名
  • isDaemon:是否是守护线程,缺省的时候默认为否,有啥区别请自行了解,有机会的话我也会整理笔记

核心方法

核心方法有添加任务、取消任务和净化三种

  • 添加任务有6中公用方法(实际最后使用同一种私有方法)
    • schedule(TimerTask task, long delay):指定任务task,在delay毫秒延迟后执行
    • schedule(TimerTask task, Date time):指定任务task,在time时间点执行一次
    • schedule(TimerTask task, long delay, long period):指定任务task,延迟delay毫秒后执行第一次,并在之后每隔period毫秒执行一次
    • schedule(TimerTask task, Date firstTime, long period):指定任务task,在firstTime的时候执行第一次,之后每隔period毫秒执行一次
    • scheduleAtFixedRate(TimerTask task, long delay, long period):作用与schedule一致
    • scheduleAtFixedRate(TimerTask task, Date firstTime, long period):作用与schedule一致

    实际上最后都会使用sched(TimerTask task, long time, long period),即指定任务task,在time执行第一次,之后每隔period毫秒执行一次 schedule使用系统时间计算下一次,即System.currentTimeMillis()+periodscheduleAtFixedRate使用本次预计时间计算下一次,即time + period 对于耗时任务,两者区别较大,请按需求选择,瞬时任务无区别

  • 取消任务方法:cancel(),会将任务队列清空,并堵塞线程,且不再能够接受任务(接受时报错),并不会销毁本身的实例和其内部的线程
  • 净化方法:purge(),净化会将队列里所有被取消的任务移除,对剩余任务进行堆排序,并返回移除任务的数量

补充

  • 如何保证第一个任务是执行时间最早的 任务队列会在每一次添加任务和删除任务时,进行堆排序矫正,净化也会对剩余任务重新堆排序
  • cancel的时候线程如何处理 定时器线程进行堵塞处理,并没有销毁,在执行当前任务后就不会执行下一次了,但是**线程并没有销毁** 所以尽量不要创建太多timer对象,会增加服务器负担

3.3.使用步骤

初始化Timer

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Timer timer=new Timer();

初始化task

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private class MyTask extends TimerTask { 
     
        @Override
        public void run() { 
     
            try { 
     
                //do Something
                System.out.println(new Date().toString() + ": " + count);
                count++;
            } catch (Exception e) { 
     
                e.printStackTrace();
            }
        }
    }
}

添加任务

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
timer.schedule(myTask, 5000, 3000);

完整代码:

4.线程

线程应该是最常见的实现方案,创建一个线程执行任务即可,举例几个不同的写法,代码如下

4.1.使用thread + runnable

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.yezi_tool.demo_basic.test;

import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class ThreadTest { 
   

    private Integer count = 0;

    public ThreadTest() { 
   
        test1();
    }

    public void test1() { 
   
        new Thread(() -> { 
   
            while (count < 10) { 
   
                System.out.println(new Date().toString() + ": " + count);
                count++;
                try { 
   
                    Thread.sleep(3000);
                } catch (InterruptedException e) { 
   
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

4.2.使用线程池 + runnable

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.yezi_tool.demo_basic.test;

import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
public class ThreadTest { 
   

    private static final ExecutorService threadPool = Executors.newFixedThreadPool(5);// 线程池
    private Integer count = 0;

    public ThreadTest() { 
   
        test2();
    }

    public void test2() { 
   
        threadPool.execute(() -> { 
   
            while (count < 10) { 
   
                System.out.println(new Date().toString() + ": " + count);
                count++;
                try { 
   
                    Thread.sleep(3000);
                } catch (InterruptedException e) { 
   
                    e.printStackTrace();
                }
            }
        });
    }
}

4.3.使用ScheduledTask + runnable

ScheduledTask 有11种添加任务的方法,详情直接查看文件TaskScheduler.java,这里给出常用的几个示例

设置触发频率为3000毫秒

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.yezi_tool.demo_basic.test;

import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class ThreadTest { 
     

    private Integer count = 0;
    private final TaskScheduler taskScheduler;

    public ThreadTest(TaskScheduler taskScheduler) { 
     
        this.taskScheduler = taskScheduler;
        test3();
    }

    public void test3() { 
     
        taskScheduler.scheduleAtFixedRate(() -> { 
     
            System.out.println(new Date().toString() + ": " + count);
            count++;
        }, 3000);
    }
}

设置触发时间为每天凌晨1点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.yezi_tool.demo_basic.test;

import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class ThreadTest { 
     

    private Integer count = 0;
    private final TaskScheduler taskScheduler;

    public ThreadTest(TaskScheduler taskScheduler) { 
     
        this.taskScheduler = taskScheduler;
        test4();
    }

    public void test4() { 
     
        taskScheduler.schedule(() -> { 
     
            System.out.println(new Date().toString() + ": " + count);
            count++;
        }, new CronTrigger("0 0 1 * * ?"));
    }
}

5.quartz

专门整理了一篇quartz的笔记,有兴趣的可以看我上一篇博客

写的并不完善,后续应该会进行修正

6.总结

  • @schedule使用方便快捷,但功能有限,扩展性极低,适用于不需要统一管理的简单场景
  • Timer可以统一管理定时任务,但自身作为一个工具类,功能较少,但是也适用于很多场景了
  • 线程的使用同样比较方便,灵活度特别高,支持各种类型的触发时间,但毕竟没有专用的框架,功能并不算特别齐全,适用于对自由度要求较高的场景
  • quartz作为专门的定时器项目,功能齐全且强大,目前大部分项目仍只使用了其小部分功能,适用于要求较高的场景

7.demo地址

https://gitee.com/echo_ye/demo_basic/tree/scheduleDemo

不同定时器启用方法在README.MD中查看,一共6种方法,如有纰漏请联系我

仅实现了部分功能作为样例,请按照需求自己扩展哦,有疑问或者建议欢迎联系我~

BB两句

其实除了@schedule,其余的都可以自定义管理器,来统一管理,并动态修改,具体咋做此处先不做赘述

quartz已经整理除了静态定时器和动态定时器,有兴趣的可以瞅瞅

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/156846.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Castle DynamicProxy基本用法(AOP)
本文介绍AOP编程的基本概念、Castle DynamicProxy(DP)的基本用法,使用第三方扩展实现对异步(async)的支持,结合Autofac演示如何实现AOP编程。
拓荒者IT
2019/09/24
1.8K0
ASP.NET Core 依赖注入基本用法
ASP.NET Core从框架层对依赖注入提供支持。也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式。本文将介绍依赖注入的基本概念,并结合代码演示如何在 ASP.NET Core中使用依赖注入。
拓荒者IT
2019/09/24
2.2K0
ASP.NET Core 依赖注入基本用法
【半小时大话.net依赖注入】(一)理论基础+实战控制台程序实现AutoFac注入
其中.Net Framework框架主要以如何引入AutoFac作为容器以及如何运用AuotoFac为主,.Net Core框架除了研究引入AutoFac的两种方式,同时也运用反射技巧对其自带的DI框架进行了初步封装,实现了相同的依赖注入效果。 项目架构如下图:
梁规晓
2019/07/15
1.6K0
【半小时大话.net依赖注入】(一)理论基础+实战控制台程序实现AutoFac注入
【 .NET Core 3.0 】框架之九 || 依赖注入 与 IoC
本文有配套视频:https://www.bilibili.com/video/av58096866/?p=5 前言 1、重要:如果你实现了解耦,也就是 api 层只引用了 IService 和
老张的哲学
2022/04/10
1.2K0
【 .NET Core 3.0 】框架之九 ||  依赖注入 与 IoC
Autofac 注入仓储模式
新建IUserService,类UserService,控制器UserController
Echo_Wish
2023/11/30
3000
Autofac 注入仓储模式
asp.net core 3.1/swagger
安装nuget包:Swashbuckle.AspNetCore.SwaggerUI和Swashbuckle.AspNetCore.Annotations,配置swagger:
雪飞鸿
2020/05/25
5940
Autofac入门与替代ASP.NET Core、ABP依赖注入容器
Autofac 官网文档地址: https://autofaccn.readthedocs.io/zh/latest/index.html
痴者工良
2021/04/26
1.9K0
dotNET Core 3.X 使用 Autofac 来增强依赖注入
在上一篇《dotNET Core 3.X 依赖注入》中简单介绍了 dotNET Core 框架本身的依赖注入功能,大部分情况下使用框架的依赖注入功能就可以满足了,在一些特殊场景下,我们就需要引入第三方的注入框架。
oec2003
2020/06/11
1.8K0
dotNET Core 3.X 使用 Autofac 来增强依赖注入
【愚公系列】2023年09月 .NET CORE工具案例-JMSFramework微服务框架(RemoteLog分布式日志)
分布式日志是一种系统,其中日志消息可以在多个节点之间进行分布和复制。这样可以实现高可用性和容错性,避免单点故障和数据丢失。
愚公搬代码
2025/06/01
510
【愚公系列】2023年09月 .NET CORE工具案例-JMSFramework微服务框架(RemoteLog分布式日志)
.net 温故知新【11】:Asp.Net Core WebAPI 入门使用及介绍
在Asp.Net Core 上面由于现在前后端分离已经是趋势,所以asp.net core MVC用的没有那么多,主要以WebApi作为学习目标。
SpringSun
2023/06/09
2.5K0
.net 温故知新【11】:Asp.Net Core WebAPI 入门使用及介绍
asp.net core之Options
本文将介绍 ASP.NET Core 中的 Options 概念和使用方法。Options 是一种配置管理机制,可以将应用程序的配置信息从代码中分离出来,提高代码的可维护性和可测试性。我们将详细介绍如何定义 Options 类、注册 Options、使用 Options,并提供相应的示例代码。此外,我们还将对 IOptions、IOptionsSnapshot、IOptionsMonitor、IConfigureNamedOptions、OptionsBuilder、IValidateOptions、PostConfigureOptions 等相关接口和类进行说明,并给出使用场景示例。
饭勺oO
2023/10/18
3830
asp.net core之Options
使用 autofac 实现 asp .net core 的属性注入
使用 autofac 代替 asp .net core 默认的 IOC 容器,可实现属性注入。
jgrass
2024/12/25
3080
使用 autofac 实现 asp .net core 的属性注入
ASP.NET Core 使用Autofac
属性介绍: RegisterAssemblyTypes:寄存器程序集类型 AsImplementedInterfaces:实现的接口 InstancePerDependency:实例依赖关系 PropertiesAutowired:属性自动连接(属性自动注入)
Echo_Wish
2023/11/30
3970
ASP.NET Core 使用Autofac
Refit使用入门
原文:Refit: The automatic type-safe REST library for .NET Core, Xamarin and .NET
吴晓阳
2024/11/22
1600
【愚公系列】2022年12月 .NET CORE工具案例-性能监控工具WatchDog的使用
所谓日志(Log)是指系统所指定对象的某些操作和其操作结果按时间有序的集合。每个日志文件由日志记录组成,每条日志记录描述了一次单独的系统事件。通常情况下,系统日志是用户可以直接阅读的文本文件,其中包含了一个时间戳和一个信息或者子系统所特有的其他信息。
愚公搬代码
2022/12/07
5140
【愚公系列】2022年12月 .NET CORE工具案例-性能监控工具WatchDog的使用
ASP.NET Core 依赖注入(DI)简介
本文为官方文档译文 ASP.NET Core是从根本上设计来支持和利用依赖注入。 ASP.NET Core应用程序可以通过将其注入到Startup类中的方法中来利用内置的框架服务,并且应用程序服务也可以配置为注入。 ASP.NET Core提供的默认服务容器提供了一个最小的功能集,而不是替换其他容器。 什么是依赖注入? 依赖注入,英文是Dependency Injection一般简称DI,是实现对象与其协作者或依赖关系之间松散耦合的技术。为了执行其操作,类所需的对象不是直接实例化协作者或使用静态引用,
晓晨
2018/06/22
3.2K0
.NET 7 中 Autofac 依赖注入整合多层,项目中可直接用
1、安装Nuget包:Autofac.Extensions.DependencyInjection
郑子铭
2023/08/30
1.4K0
.NET 7 中 Autofac 依赖注入整合多层,项目中可直接用
ASP.NET Core中使用IOC三部曲(二.采用Autofac来替换IOC容器,并实现属性注入)
前言 本文主要是详解一下在ASP.NET Core中,自带的IOC容器相关的使用方式和注入类型的生命周期. 这里就不详细的赘述IOC是什么 以及DI是什么了.. emm..不懂的可以自行百度. 正文 上一篇我们说过ASP.NET Core中自带的IOC容器是属于轻量级的,功能并不是很多,只是提供了基础功能而已.. 所以今天我们主要讲讲如何采用Autofac来替换IOC容器,并实现属性注入 注意:本文需要读者理解DI IOC并使用过相关框架. 1.将默认的IOC容器替换为Autofac  首先,我们需要从nu
GuZhenYin
2018/03/30
1.2K0
ASP.NET Core中使用IOC三部曲(二.采用Autofac来替换IOC容器,并实现属性注入)
个人项目框架搭建 -- Autofac简单使用记录
1、添加autofac相关程序集/使用Nuget 2、引入命名空间 using Autofac; using Autofac.Configuration; 3、使用 3.1:直接使用 var build = new ContainerBuilder(); build.RegisterType<MemoryCacheManager>(); build.Register<ICacheManager>(c => new MemoryCacheManager()).InstancePerLifetimeScope(
易墨
2018/09/14
7690
.NET程序对接 OpenTelemetry logs
OpenTelemetry 是一个由 CNCF(Cloud Native Computing Foundation)托管的开源项目,旨在为观察性(Observability)提供一套全面的工具,包括度量(Metrics)、日志(Logs)和追踪(Traces)。它的目标是为所有类型的遥测数据提供一种标准化的方法。 OpenTelemetry 提供了一套 API 和 SDK,使得开发者可以在他们的应用程序中生成和收集遥测数据。此外,OpenTelemetry 还提供了一套收集器(Collector),可以接收、处理和导出遥测数据,以便于后续的分析和可视化。 本文不是用来介绍 OpenTelemetry 的,所以更多的细节就不提了。
MJ.Zhou
2024/06/04
2500
.NET程序对接 OpenTelemetry logs
推荐阅读
相关推荐
Castle DynamicProxy基本用法(AOP)
更多 >
LV.0
这个人很懒,什么都没有留下~
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验