目录
一、定时器简单介绍
定时器就相当于是闹钟
在网络编程中定时器非常关键的
比如浏览器浏览某个网页网卡了,浏览器就会转圈圈(阻塞等待),这个等待不是无限等待,等到一定时间之后就显示访问超时
使用阻塞队列能够达到削峰填谷的效果,但是在削峰中会有大量的数据涌入队列,如果后续的服务器消费速率比较慢的话,队列里的有些元素可能要滞留很久,而浏览器在访问一段时间之后就会显示访问超时,这个时候即使服务器处理了这些信息也无法响应在对应的服务器上了,所以在这里可以使用定时器,让那些滞留时间太久的请求被删掉
在前端开发中也非常依赖定时器
例如,很多网站上有一些动画效果,也是通过定时器,比如每隔30秒,把页面往下滚动几个像素
二、定时器的使用
在Java标准库中,提供了这个定时器组件Timer
schedule:安排
import java.util.Timer;
import java.util.TimerTask;
public class ThreadDemo25 {
public static void main(String[] args) {
System.out.println("代码开始执行");
Timer timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("触发定时器");
}
},3000);
}
}
执行结果
三、定时器的实现
一个定时器里面是可以安排很多任务的,这些任务会按照时间,谁先到了时间就先执行谁
描述任务:直接使用Runnable来描述这个任务
组织任务:需要一个数据结构把很多任务放到一起,此处的需求是需要在一大堆任务中找到那个时间最先结束的任务,于是我们想到了优先级队列,此处我们最好选用带阻塞功能的优先级队列,目的是未来线程安全,而且标准库中也提供带阻塞功能的优先队列
具体思路为
1.提供一个schedule方法,这个方法是往阻塞队列中插入元素
2.需要让timer内部有一个线程,让这个线程一直去扫描队首元素,看看这个队首元素是不是到点了如果到点了,就执行这个任务,如果没到点,就把这个元素重新放回去,然后继续扫描
从上面的代码中,我们可以发现一个问题:就是 扫描的速度太快了
比如说最早的任务是早上8:00执行,但是当前才7:00,如果不采取措施的话,这个循坏会一直执行,而且执行的非常快,一秒就能转几万次,这段时间完全就是空转,完全没意义,我们称之它为“忙等”
此时我们想到了之前学过的sleep和wait
我们可以一个个分析
sleep和wait都可以指定一个时间作为参数,也就是等待的时间
等待的时间可以通过当前时间和首个任务之间的时间来算
就用刚刚的例子说明,wait需要等待1小时,sleep也需要等待1小时,但是wait可以,sleep却不行,具体原因是wait(1小时)是可以提前唤醒的,而sleep(1小时)不可以,如果中途加了一项需要7:30执行的任务,wait可以通过notify来唤醒,及时感知7:30的任务去执行,但sleep就错过这次的任务了
import java.util.concurrent.PriorityBlockingQueue;
public class ThreadDemo26 {
//使用这类来描述任务
static class Task implements Comparable<Task>{
//command表示这个任务是啥
private Runnable command;
//time表示这个任务什么时候到时间
//这里的time使用毫秒级的时间戳
private long time;
//约定参数time是一个时间差(类似与3000ms)
//希望this.time来保存一个绝对的时间(毫秒级时间戳)
public Task(Runnable command, long time) {
this.command = command;
//System.currentTimeMillis()表示当前的时间,再加上time表示这个任务要执行的时间
this.time = System.currentTimeMillis()+time;
}
public void run(){
command.run();
}
@Override
public int compareTo(Task o) {
return (int)(this.time-o.time);
}
}
//写这个内部类是避免和标准库中的Timer冲突
static class Timer{
//使用这个带优先级版本的阻塞队列来组织任务
private PriorityBlockingQueue<Task> queue=new PriorityBlockingQueue<>();
//使用locker对象来解决忙等问题
private Object locker=new Object();
public void schedule(Runnable command,long delay){
Task task=new Task(command,delay);
queue.put(task);
//每次插入新的任务都要唤醒扫描线程
//让扫描线程能够重新计算wait的时间
//保证新的任务不会错过
synchronized (locker){
locker.notify();
}
}
public Timer(){
//创建一个扫描线程,这个线程用来判定当前的任务是不是已经到时间了
Thread t=new Thread(){
@Override
public void run() {
while(true){
//取出队列的首元素,判定是不是到时间了
try {
Task task=queue.take();
long curTime=System.currentTimeMillis();
if(task.time>curTime){
//说明时间还没到,暂时不执行
//前面的take操作会把队首元素给删除掉
//但是此时队首的元素还没有执行,所以不能删除,要重新把它给加回来
queue.put(task);
//进行等待
synchronized (locker){
locker.wait(task.time-curTime);
}
}else{
//时间到了
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
//如果出现了interrupt()方法,就能够退出线程
break;
}
}
}
};
t.start();
}
public static void main(String[] args) {
System.out.println("程序启动");
Timer timer=new Timer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
},3000);
}
}
}
运行结果