标准库中的定时器
实现一个定时器
定时器完整版代码
Java标准库中提供了一个Timer类, 通过Timer类中的核心方法schedule()即可创建一个定时器.
public static void main(String[] args) {
//创建Timer对象
Timer timer = new Timer();
//调用schedule()方法创建定时器
//schedule()方法共有两个参数
//1. 实现TimerTask接口, 并重写run()方法来给定时器布置任务
//2. 设置时间(定时器多长时间后开始运行)
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("world");
}
//这个3000代表3000ms(也就是3s)后开始执行run()方法中的内容
},3000);
System.out.print("hello ");
}
//结果:
//先打印hello, 三秒后打印定时器中的world
hello world
//打印完成后, 程序并未结束
定时器具备的功能:
初步实现:
//创建一个类表示定时器中的任务
//要实现comparable接口来让优先级队列按照队列中元素的执行时间来比较
class MyTask implements Comparable<MyTask>{
//任务的具体工作
Runnable runnable;
//任务的执行时间
private long time;
//生成构造器, after是一个时间间隔, 代表after毫秒后任务开始执行
public MyTask(Runnable runnable, long after) {
this.runnable = runnable;
//任务的执行时间为当前时间+after
this.time = System.currentTimeMillis()+after;
}
public long getTime() {
return time;
}
public void run(){
runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
//创建定时器
class MyTimer{
//使用线程安全的优先级队列存储多个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
public MyTimer() {
Thread thread = new Thread(() ->{
while (true){
try {
//取出队首元素, 判断队首元素的任务是否需要执行
MyTask myTask = queue.take();
//当前时间小于任务的执行时间, 也就是还没有到执行时间
if(System.currentTimeMillis()<myTask.getTime()){
queue.put(myTask);
}
//时间到了, 需要执行任务
else {
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
public void schedule(Runnable runnable, long after){
MyTask task = new MyTask(runnable,after);
queue.put(task);
}
}
上面的代码中, 我们实现了一个计数器, 用户可以通过创建MyTimer对象, 并调用schedule()方法来使用这个定时器:
//使用定时器(使用方法与Java标准库中的类似)
public class Demo {
public static void main(String[] args) {
MyTimer timer = new MyTimer();
timer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("world");
}
},3000);
System.out.print("hello ");
}
}
//结果:
//先打印hello, 三秒后打印定时器中的world
hello world
//打印完成后, 程序并未结束
"忙等"问题
这个定时器基本已经完成了, 但还存在一定的问题, 假设某个时刻, thread线程将队首元素取出, 如果这个元素还没有到执行时间, thread线程会将该元素放回队列, 然后再取出, 再放回去.
就好比我30分钟后要出门, 但这30分钟内, 我每一秒都在看时间, 直到30分钟后, 我才会停止, 这种既没有任何实质性的工作, 有没有任何产出的行为, 我们称之为"忙等".
那么, 如何解决忙等问题?
我们可以通过wait()方法来解决, wait()方法不但可以通过notify()方法来唤醒, 还可以自定义等待时间, 一旦等待时间结束后, 等待状态便会自动解除, 因此, 我们只需计算出当前时间与任务开始时间的差值, 即可解决线程的"忙等"问题.
synchronized (locker){
//等待时间为任务执行时间 - 当前时间
locker.wait(myTask.getTime() - System.currentTimeMillis());
}
wait()方法能否替换为sleep()方法?
不可以. wait()方法在等待过程中可以被唤醒, 而sleep()不可以, 在等待过程中, 如果有执行时间更早的任务入队列, 此时就需要将正在休眠的任务唤醒.
//创建一个类表示定时器中的任务
class MyTask implements Comparable<MyTask>{
//任务的具体工作
Runnable runnable;
//任务的执行时间
private long time;
//生成构造器, after是一个时间间隔, 代表after毫秒后任务开始执行
public MyTask(Runnable runnable, long after) {
this.runnable = runnable;
//任务的执行时间为当前时间+after
this.time = System.currentTimeMillis()+after;
}
public long getTime() {
return time;
}
public void run(){
runnable.run();
}
@Override
public int compareTo(MyTask o) {
return (int) (this.time - o.time);
}
}
class MyTimer{
//使用线程安全的优先级队列存储多个任务
private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
//设置等待时间
Object locker = new Object();
public MyTimer() {
Thread thread = new Thread(() ->{
while (true){
try {
//取出队首元素, 判断队首元素的任务是否需要执行
MyTask myTask = queue.take();
//当前时间小于任务的执行时间, 也就是还没有到执行时间
if(System.currentTimeMillis()<myTask.getTime()){
//wait()方法需要搭配synchronized使用
synchronized (locker){
//等待时间为任务执行时间 - 当前时间
locker.wait(myTask.getTime() - System.currentTimeMillis());
}
queue.put(myTask);
}
//时间到了, 需要执行任务
else {
myTask.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
public void schedule(Runnable runnable, long after){
MyTask task = new MyTask(runnable,after);
//入队列
queue.put(task);
//每次入队列之后, 执行一次唤醒操作
//因为新入队任务的执行时间可能早于正处于休眠状态的任务的执行时间
synchronized (locker){
locker.notify();
}
}
}