网站首页 > java教程 正文
1.定时器
1.1 含义
??在Java中,定时器(Timer)是一个工具类,用于安排任务(Task)在指定时间后执行或以指定的时间间隔重复执行。它可以用于执行定时任务、定时调度和时间延迟等操作。
定时器(Timer)可以应用于许多场景,比如:
- 调度任务:当你需要按照预定时间执行任务时,可以使用定时器。例如,每天凌晨执行数据备份、定时生成报表、定时发送通知等。
- 超时处理:当你需要处理某个操作的超时情况时,可以使用定时器。例如,设置一个操作的超时时间,如果在规定时间内未完成,则执行相应的超时处理逻辑。
1.2 标准库中的定时器
??Java中的定时器:java.util.Timer,它的常用方法:
方法 | 描述 |
schedule(TimerTask task, Date time) | 安排在指定时间执行任务。 |
schedule(TimerTask task, long delay) | 安排在指定延迟时间后执行任务。 |
schedule(TimerTask task, long delay, long period) | 安排在指定延迟时间后以指定的时间间隔重复执行任务。 |
scheduleAtFixedRate(TimerTask task, Date firstTime, long period) | 安排在指定时间开始以固定的时间间隔重复执行任务。 |
scheduleAtFixedRate(TimerTask task, long delay, long period) | 安排在指定延迟时间后以固定的时间间隔重复执行任务。 |
cancel() | 取消定时器的所有任务。 |
purge() | 从定时器的任务队列中删除所有已取消的任务。 |
java复制代码public class Main {
public static void main(String[] args) {
Timer timer = new Timer();
//调度指定的任务在指定的延迟时间(3000ms)后执行。
timer.schedule(new TimerTask() {
//待执行的任务
@Override
public void run() {
System.out.println("hello");
}
},3000);
}
}
也可以一次注册多个任务:
java复制代码public class Main {
public static void main(String[] args) {
Timer timer = new Timer();
//在指定的延迟时间(1000ms)后执行。
timer.schedule(new TimerTask() {
//待执行的任务
@Override
public void run() {
System.out.println("任务1");
}
},1000);
//在指定的延迟时间(2000ms)后执行。
timer.schedule(new TimerTask() {
//待执行的任务
@Override
public void run() {
System.out.println("任务2");
}
},2000);
//在指定的延迟时间(3000ms)后执行。
timer.schedule(new TimerTask() {
//待执行的任务
@Override
public void run() {
System.out.println("任务3");
}
},3000);
}
}
2.简单模拟实现定时器
2.1 实现思路
- 使用一个数据结构来保存所有的任务,这些任务是根据时间的大小来进行先后执行的,所以这里使用优先级队列。由于这里是多线程的环境,所以这里采用PriorityBlockingQueue(优先级阻塞队列),时间越小优先级越高。
- 我们需要使用一个线程来扫描定时器里面的任务是否到达执行时间,由于我们采用的是优先级队列数据结构,所以只需扫描队首元素。如果队首还没到执行时间,那么后面的元素不可能到达执行时间。
- 任务用一个类MyTask来表示,这里需要实现Comparable接口,因为它需要存入优先级队列。其中的属性:
java复制代码//表示定时器中的任务
class MyTask implements Comparable<MyTask>{
//要执行的任务内容
private Runnable runnable;
//延迟时间
private long time;
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
//为了便于后面的比较,需要提供 get 方法
public long getTime() {
return time;
}
//表示任务开始执行
public void run(){
this.runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int)(this.getTime() - o.getTime());
}
}
- 实现添加任务的方法schedule:
java复制代码public class MyTimer {
//扫描线程
private Thread thread;
//优先级队列(这里为阻塞队列)
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
/**
* 这个方法是用来注册(添加)任务的
* @param runnable 表示待执行的任务
* @param after 表示多少时间过后执行任务
*/
public void schedule(Runnable runnable,long after){
//添加任务,注意这里的时间是 System.currentTimeMillis() + after
MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
queue.put(task);
}
}
- 添加一个线程来检测队首元素:
java复制代码 //当创建对象的时候就直接开启一个线程
public MyTimer(){
thread = new Thread(()->{
while(true){
//取出队首,如果到时间了就执行。
try {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
//时间未到,不执行
queue.put(myTask);
}else {
//时间已到,执行
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
??就这样就完了吗?其实不然,在上面代码中while (true)转的太快了, 造成了无意义的 CPU 浪费,如果第一个任务设定的是 1 min 之后执行某个逻辑,那么在这一分钟内 CPU 会一直存取队首元素。所以这里需要借助该对象的wait / notify来解决 while (true) 的忙等问题。
java复制代码 public MyTimer(){
thread = new Thread(()->{
while(true){
//取出队首,如果到时间了就执行。
try {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
queue.put(myTask);
//时间未到,不执行,这里的 this 表示 MyTimer 对象
synchronized (this){
//阻塞一段时间
this.wait(myTask.getTime() - curTime);
}
}else {
//时间已到,执行
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
/**
* 这个方法是用来注册(添加)任务的
* @param runnable 表示待执行的任务
* @param after 表示多少时间过后执行任务
*/
public void schedule(Runnable runnable,long after){
//添加任务,注意这里的时间是 System.currentTimeMillis() + after
MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
queue.put(task);
synchronized(this){
this.notify();
}
}
??修改 Timer 的 schedule 方法,每次有新任务到来的时候唤醒一下线程。(因为新插入的任务可能是需要马上执行的)。
??还没结束!上面的代码还是有缺陷的。假设当 thread 线程执行完 queue.take() 过后,myTask.getTime() - curTime 的值为 1 个小时。这时 CPU 调度了其它线程(假设为 t2) 执行, t2 线程调用 schedule 方法,延时时间为 30 分钟,并调用 put 方法,随后再执行 notify 方法。然而这时 wait 方法还没有执行,notify 相当于失效了。这时CPU再调度 thread 线程执行,但是 myTask.getTime() - curTime 的值本应是 30 分钟(新添加了一个任务),但是实际上却是 1 个小时。 ??这是因为queue.take()与wait不是原子操作,所以才导致这个问题的发生,下面是改进后的代码。
java复制代码 public MyTimer(){
thread = new Thread(()->{
while(true){
//取出队首,如果到时间了就执行。
try {
synchronized (this){
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
queue.put(myTask);
//时间未到,不执行
//阻塞一段时间
this.wait(myTask.getTime() - curTime);
}else {
//时间已到,执行
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
2.2 完整代码
java复制代码//表示定时器中的任务
class MyTask implements Comparable<MyTask>{
//要执行的任务内容
private Runnable runnable;
//延迟时间
private long time;
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
//为了便于后面的比较,需要提供 get 方法
public long getTime() {
return time;
}
//表示任务开始执行
public void run(){
this.runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int)(this.getTime() - o.getTime());
}
}
public class MyTimer {
//扫描线程
private Thread thread;
//优先级队列
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public MyTimer(){
thread = new Thread(()->{
while(true){
//取出队首,如果到时间了就执行。
try {
synchronized (this){
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if(curTime < myTask.getTime()){
queue.put(myTask);
//时间未到,不执行
//阻塞一段时间
this.wait(myTask.getTime() - curTime);
}else {
//时间已到,执行
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
/**
* 这个方法是用来注册(添加)任务的
* @param runnable 表示待执行的任务
* @param after 表示多少时间过后执行任务
*/
public void schedule(Runnable runnable,long after){
//添加任务,注意这里的时间是 System.currentTimeMillis() + after
MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
queue.put(task);
synchronized(this){
this.notify();
}
}
}
猜你喜欢
- 2024-09-09 Java 定时器、加密、File类(java定时器时间格式)
- 2024-09-09 100个Java工具类之67:定时执行Timer
- 2024-09-09 JAVA定时器的嵌套实现(java的定时器能不能提供实时保证 可能提前也可能推迟)
- 2024-09-09 Java中分布式定时器实现和原理介绍
- 2024-09-09 Java定时任务——SpringTask(java定时任务的实现方式)
- 2024-09-09 全面了解Java Timer定时器类(java定时器怎么用)
- 2024-09-09 Java多线程19:定时器Timer(java多线程实现方式)
- 2024-09-09 Java 中如何实现定时任务(java定时任务底层原理)
- 2024-09-09 Java定时任务的五种创建方式,你都会么?
- 2024-09-09 Java中实现定时任务的几种方式(java中实现定时任务的几种方式有哪些)
你 发表评论:
欢迎- 最近发表
-
- Java常量定义防暴指南:从"杀马特"到"高富帅"的华丽转身
- Java接口设计原则与实践:优雅编程的艺术
- java 包管理、访问修饰符、static/final关键字
- Java工程师的代码规范与最佳实践:优雅代码的艺术
- 编写一个java程序(编写一个Java程序计算并输出1到n的阶乘)
- Mycat的搭建以及配置与启动(mycat部署)
- Weblogic 安装 -“不是有效的 JDK Java 主目录”解决办法
- SpringBoot打包部署解析:jar包的生成和结构
- 《Servlet》第05节:创建第一个Servlet程序(HelloSevlet)
- 你认为最简单的单例模式,东西还挺多
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)