【JAVA定时器】四种常见定时器的原理和简单实现
个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道
如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充
前言
定时器顾名思义,即定时触发某个事件,分离开来,即包含三个因素:定时,触发,某个事件,本文也将以此为基础介绍五种常见的定时器
本文只做基于SpringBoot的示例,其余版本的请自行查阅资料,大同小异
1.介绍
1.1.目的
定时器的目的即为了在某个时间点,程序自身主动触发某个事件,而不需要外力去开启或者启动,以节省人力并统一管理
1.2.示例场景
- 管理系统,需要每日12点将前一天的数据进行备份,并生成历史数据统计
- 宿管系统,每日10点将所有未归人员统计出来,主动交由管理人员
- 硬件设备,需要每隔2分钟检查设备是否连接正常,设备异常需要更新状态到管理端,必要时通知有关人员
- 图书馆借书管理系统,每天12点需要检查即将超时和已超时归还的书籍,并通过短信或其他途径通知有关人员
- 手机下载管理系统,开启下载后每隔0.5s刷新一次下载进度,在下载完成或者长时间卡顿时告知用户
- 订单管理系统,用户下达订单后开需要在半小时内付款,成功付款则生成订单结果,超时未付款则自动取消订单
是不是觉得很常见?
1.3.常见实现方案
- @Scheduled注解:基于注解
- Timer().schedule创建任务:基于封装类
Timer
- 线程:使用线程直接执行任务即可,可以与thread、线程池、ScheduleTask等配合使用
- quartz配置定时器:基于
spring
的quartz
框架
本文仅简述前3种,比较简单易懂,quartz会专门分离出来整理
2.@Scheduled注解
2.1.介绍:
使用注解标记需要定时执行的方法,并设置执行时间,便可使其在指定的时间执行指定方法
2.2.步骤:
- 使用注解
@Scheduled
标记目标方法,参数为执行时间 - 使用注解
@EnableScheduling
标记目标方法所在的类,或者直接标记项目启动类
2.3.注解:
- 注解
@Scheduled
为方法注解,用于标记某个方法在何时定时执行 - 需要配合另一个注解
@EnableScheduling
进行使用,该注解用于标记某个类,开启定时任务,通常标记在定时器所在的类,或者直接设置在项目启动类上
2.4.@Scheduled参数:
@Scheduled(fixedDelay = 5000)
:方法执行完成后等待5秒再次执行@Scheduled(fixedRate = 5000)
:方法每隔5秒执行一次@Scheduled(initialDelay=1000, fixedRate=5000)
:延迟1秒后执行第一次,之后每隔5秒执行一次fixedDelayString
、fixedRateString
、initialDelayString
:与上诉三种作用一直,但参数为字符串类型,因而可以使用占位符,形如@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秒执行一次
@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秒一次
@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.样例
使用非常简单,这里先给出样例,在对照进行介绍
代码如下
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() {@Overridepublic void run() {try {//do SomethingSystem.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拥有一个私有的TaskQueue和TimerThread,
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()+period
而
scheduleAtFixedRate
使用本次预计时间计算下一次,即time + period
对于耗时任务,两者区别较大,请按需求选择,瞬时任务无区别
取消任务方法:cancel(),会将任务队列清空,并堵塞线程,且不再能够接受任务(接受时报错),并不会销毁本身的实例和其内部的线程
净化方法:purge(),净化会将队列里所有被取消的任务移除,对剩余任务进行堆排序,并返回移除任务的数量
补充
如何保证第一个任务是执行时间最早的
任务队列会在每一次添加任务和删除任务时,进行堆排序矫正,净化也会对剩余任务重新堆排序
cancel的时候线程如何处理
定时器线程进行堵塞处理,并没有销毁,在执行当前任务后就不会执行下一次了,但是**线程并没有销毁**
所以尽量不要创建太多timer对象,会增加服务器负担
3.3.使用步骤
初始化Timer
Timer timer=new Timer();
初始化task
private class MyTask extends TimerTask {@Overridepublic void run() {try {//do SomethingSystem.out.println(new Date().toString() + ": " + count);count++;} catch (Exception e) {e.printStackTrace();}}} }
MyTask myTask=new MyTask();
添加任务
timer.schedule(myTask, 5000, 3000);
完整代码:
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() {testTimer2();}public void testTimer2() {Timer timer = new Timer();MyTask myTask = new MyTask();timer.schedule(myTask, 0, 1000);}private class MyTask extends TimerTask {@Overridepublic void run() {try {//do SomethingSystem.out.println(new Date().toString() + ": " + count);count++;} catch (Exception e) {e.printStackTrace();}}}}
当然可以缩写为样例里面的写法,更加简洁,请按照自己需求修改
4.线程
线程应该是最常见的实现方案,创建一个线程执行任务即可,举例几个不同的写法,代码如下
4.1.使用thread + runnable
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
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毫秒
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点
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已经整理除了静态定时器和动态定时器,有兴趣的可以瞅瞅
作者:Echo_Ye
WX:Echo_YeZ
EMAIL :echo_yezi@qq.com
个人站点:在搭了在搭了。。。(右键 - 新建文件夹)
【JAVA定时器】四种常见定时器的原理和简单实现相关推荐
- 四种常见的激活函数画图及简单改造
原文:https://blog.csdn.net/qq_33039859/article/details/79520092 # -*- coding: utf-8 -*- ""&q ...
- JAVA对象JSON数据互相转换的四种常见情况
这篇文章主要介绍了JAVA对象JSON数据互相转换的四种常见情况,需要的朋友可以参考下 1. 把java 对象列表转换为json对象数组,并转为字符串 复制代码代码如下: JSONArray ...
- JavaScript内存管理机制以及四种常见的内存泄漏解析
转自:http://geek.csdn.net/news/detail/238898 原文:How JavaScript works: memory management + how to handl ...
- Java中 9 种常见的 CMS GC 问题分析与解决
目录 Java中 9 种常见的 CMS GC 问题分析与解决 1. GC 1.1 引言 1.2 概览 2. GC 基础 2.1 基础概念 2.2 JVM 内存划分 2.3 分配对象 2.4 收集对象 ...
- JDK四种常见线程池及使用场景、两种提交任务的方法
转载:JDK 提供的线程池及使用场景 - 编程猎人 (programminghunter.com) 目录 四种常见线程池 1.newFixedThreadPool 2.newSingleThreadE ...
- 你知道Java的四种引用类型吗?
点击蓝字 关注我们 点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 01 概述 在Java中提供了四个级别的引用:强引用,软引用,弱引用和虚引用. ...
- Java的四种引用方式
Java的四种引用方式 java内存管理分为内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向该对象. java对象的引用包括 强引用,软引用,弱引用,虚引用 Jav ...
- java中的强软弱虚_详解Java的四种引用——强软弱虚,颠覆你的认知!
强软弱虚 java中的数据被类型分为了两类,它们分别是基本类型和引用类型.一般我们new出来的对象都属于引用类型的范畴.我们知道java是有垃圾回收机制的一种语言,根据垃圾回收时的策略,java将对于 ...
- [转载] application/json 四种常见的 POST 提交数据方式
参考链接: 关于/联系我 application/json 四种常见的 POST 提交数据方式 转载声明: 本文系转载自以下两篇文章: 四种常见的 POST 提交数据方式 作者: 沧海一滴 转载仅为方 ...
最新文章
- MySQL数据库:触发器Trigger
- 如何在Flutter(REST API)中进行API调用
- twitter 数据集处理_Twitter数据清理和数据科学预处理
- 《C程序设计语言》-第2章-习题
- NM的完整形式是什么?
- 基于javaweb的黑马旅游网站来源_喜讯丨创业黑马(重庆)科技孵化中心今日开业!助力重庆高新区打造人工智能创新高地...
- c#读蓝牙数据_C#读取BWT901CL蓝牙传感器的数据
- graphpad多条不同的曲线_应用Graphpad Prism制作多组ROC曲线图
- 连续语音识别,continuous speech recognition,音标,读音,翻译,英文例句,英语词典
- iOS 打电话 发短信功能的实现
- 大学,学会这样上网才有意义!
- 软件项目风险评估报告
- javaweb表格制作举例
- 数理逻辑蕴含_16-证明逻辑等价式和逻辑蕴涵式
- java微信公众号素材管理系统_微信公众平台后台素材管理
- 反燃油车占位方案:AI识别+EasyCVR解决燃油车占位问题
- 手机android怎么开机画面,安卓开机动画特效软件下载
- 数据结构—1.时间复杂度
- leetcode 5473. 灯泡开关 IV medium (智力题,O(n)思路详解)
- 推荐|从云生物科技行业数据安全解决方案