一,定时器
1.定时器的概念
定时器是Java开发中一个重要的组件(功能类似于闹钟),可以指定一个任务在多长时间后执行(尤其在网络编程的时候,如果网络卡顿很长时间没有响应用户的需求,此时可以使用定时器来终止用户的请求),所以一个定时器最少具有两个功能:
一个需要执行的任务
指定的时间去执行任务
在Java标准中提供了Timer类来封装定时器这样的功能。
2.Timer类
Timer类是Java标准库提供的内置类,其核心方法是schedule方法;
Timer类构造方法
Timer() | 创建一个新的计时器 |
Time(boolean isDaemon) | 创建一个新的定时器,其相关线程可以指定为 run as a deamon |
Time(String name) | 创建一个新的定时器,其相关线程具有指定的名称 |
Time(String name, boolean isDaemon) | 创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a deamo |
Timer类的构造方法一共有4种,一般最常用的就是第一种无参的构造方法
schedule方法
schedule方法是Timer类的核心,定时器所执行的操作都是由该方法提供,所以我们来看一下schedule方法的源码:

该方法是一个普通方法,在使用前需要先创建Timer类对象
该方法有两个参数:第一个是TimerTask task(重写内部的run方法来指定定时器的任务),第二个是long delay(描述的是多长时间后执行该任务,单位是ms)
其中这里的TimerTask类似于之前创建的线程中的Runnable,都是指定一个具体的任务
定时器任务示例
/**
* 创建一个定时器任务
* 任务内容:输出hello world
* 时间:1000ms之后执行
*/
import java.util.Timer;
import java.util.TimerTask;
public class ThreadDemo2 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello world");
}
},1000);
}
}
执行结果:

程序将会在1000ms毫秒之后打印“hello word”,但是此时进程并没有结束,因为定时器创建出来的线程默认属于前台线程,前台线程会阻止进程的结束。
二,模拟实现简单的定时器
定时器需要满足两个功能:
1.让被注册的任务能够在指定的时间被执行
2.一个定时器可以注册N个任务,N个任务按照最初约定的时间,按顺序执行
解决办法:
对于第一个功能,我们只需要单独在定时器内部搞个线程,让这个线程周期性的扫描,判定任务是否到时间了,如果到时间了就执行,否则就不执行
对于第二个问题,我们需要一个数据结构来保存这些任务,因为任务需要按照时间的先后顺序来执行,所以这里采用优先级队列来保存任务(同时这个优先级队列需要按照指定的优先级来保存),又因为在多线程的环境下使用定时器,所以优先级队列需要带有阻塞功能,即采用PriorityBlockingQueue这样的数据结构。
代码如下:
package ThreadLearning;
import java.util.concurrent.PriorityBlockingQueue;
/**
* 使用一个类来表示一个定时器中的任务
*/
class MyTask implements Comparable<MyTask> {
//要执行的任务
private Runnable runnable;
//任务啥时候执行(使用毫秒时间戳表示)
private long time;
public MyTask(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
//获取当前任务的时间
public long getTime() {
return time;
}
//执行时间
public void run() {
runnable.run();
}
//重写compareTo方法来指定优先级队列的优先级
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
class MyTimer {
//扫描线程
private Thread t = null;
//阻塞队列用来保存任务
//传入的参数是MyTask这个类
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public MyTimer() {
t = new Thread(() -> {
while (true) {
try {
synchronized (this) {
MyTask myTask = queue.take();
long curTime = System.currentTimeMillis();
if (curTime < myTask.getTime()) {
//还没到点,先不执行
queue.put(myTask);
//设置wait的等待时间,即队首元素与现在时间的差值
this.wait(myTask.getTime() - curTime);
} else {
//时间到了,执行任务
myTask.run();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
/**
* 该方法本身很简单,只是单纯把任务放到队列中,指定两个参数
*
* @param runnable 任务内容
* @param after 任务在多少毫秒之后执行
*/
public void schedule(Runnable runnable, long after) {
//注意这里时间上的换算
MyTask myTask = new MyTask(runnable, System.currentTimeMillis() + after);
queue.put(myTask);
//只要有新的线程加入就会唤醒wait
synchronized (this) {
this.notify();
}
}
}
测试:
/**
* 分别创建三个任务,三个任务的执行时间分别是1000 2000 3000
* 如果打印结果分别按照顺序执行则正确
*/
public class ThreadTest {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务1");
}
}, 1000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务2");
}
}, 2000);
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务3");
}
}, 3000);
}
}

总结:
这里会按照设定时间的先后打印任务,发现此时打印完三个任务之后程序没有停止,因为在刚刚模拟实现的定时器中采用了while(true)的操作,会一直循环从阻塞队列中获取任务,队列为空就会发生阻塞等待(标准库中的定时器也是想相同的逻辑),默认为前台线程;
因为采用了优先级队列的数据结构,所以对自己定义的MyTask类一定要设置优先级,需要实现Compareable接口重写compareTo方法;
这里一定要用wait方法设置一个时间爱参数避免“死等”的现象,从而浪费CPU资源

在schedule方法中设置notify方法来唤醒,每次注册一个任务之后就进行判读时间是否到了是否需要执行
