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() |
从定时器的任务队列中删除所有已取消的任务。 |
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);
}
}
也可以一次注册多个任务:
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
接口,因为它需要存入优先级队列。其中的属性://表示定时器中的任务
class MyTask implements Comparable{
//要执行的任务内容
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
:public class MyTimer {
//扫描线程
private Thread thread;
//优先级队列(这里为阻塞队列)
private PriorityBlockingQueue 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);
}
}
//当创建对象的时候就直接开启一个线程
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)
的忙等问题。
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
不是原子操作,所以才导致这个问题的发生,下面是改进后的代码。
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 完整代码
//表示定时器中的任务
class MyTask implements Comparable{
//要执行的任务内容
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 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();
}
}
}