在学习“定时器”之前,我们先来清楚的了解定时器的应用需求,或者场合。当需要在一段规定的时间内完成某些操作时,就需要有一个精准的定时工具来提醒你更好的去完成。
我自己理解的是“定时”是,在规定好的时间一旦到了,就自动“醒来”一次,当然,可以给这个过程启动一个线程。我觉得他更多起到的是一个“提醒”的作用,跟你本身用定时器去做什么,或者还有能不能在定时器规定的时间内完成你的任务,跟这些都没有关系。了解了这些基本知识之后就来具体通过一个简单例子来说明定时器的作用;
首先,定时器工作需要的属性有时延,要开启一个线程协助在一定时间段到了“醒来”,还需要有volatile类型的goon控制进程的运行,此外考虑到在定时期间要做一件事不能被其他的事务干扰,因为还需要设置一个锁lock,保证一件事能够正常的完成。下面来分析代码;
public abstract class SimpleDidadida implements Runnable{
public static final long DELAY = 1000;
private long delay;
private volatile boolean goon;
private Object lock;
public SimpleDidadida() {
this(DELAY);
}
public SimpleDidadida(long delay) {
this.delay = delay;
this.lock = new Object();
}
public abstract void doing();
public void start() {
if(this.goon == true) {
return;
}
this.goon = true;
new Thread(this).start();
}
public void stop() {
if(this.goon == false) {
return;
}
this.goon = false;
}
@Override
public void run() {
while(goon) {
synchronized (lock) {
try {
lock.wait(delay);
doing();
} catch (InterruptedException e) {
goon = false;
}
}
}
}
}
上述写到一旦开始定时,就开启一个线程,线程中用到了是“锁“,每”沉睡“一段时间就去做某件事,这里做一件事用到了抽象方法,也就是说现在不需要马上确定要做的事具体是啥,可以在它的实现类中再去完成方法的具体操作。但是实际上上述的方法存在一定的缺陷,先线程中的doing()方法运行会占用一定的时间,导致定时不够”精准“,所以需要改进,可以把doing()要做的事当成一个线程来,在定时”醒来“时要开始做某件事,直接开启做这件事的线程,紧接着就进行后续的操作,这样作最大程度的避免了完成事件所占用的时间,让定时更精准。下面是改进的部分;
@Override
public void run() {
while(goon) {
synchronized (lock) {
try {
lock.wait(delay);
new InnerWorker();
} catch (InterruptedException e) {
goon = false;
}
}
}
}
private class InnerWorker implements Runnable {
public InnerWorker() {
new Thread(this).start();
}
@Override
public void run() {
doing();
}
}
下面给出上述抽象类的实现类,需要实现抽象类中未实现的方法doing(),在这我们只是给出了一个简单的例子,来实现定时器的“定时”功能,定时期间要做的事只是简单的输出此刻的系统时间,为了更方便理解,还输出了每一次的开始和结束,下面来看具体的代码;
public class DemoDidadida {
private SimpleDidadida simpleDidadida;
private int time = 0;
public DemoDidadida() {
this.simpleDidadida = new SimpleDidadida(500) {
@Override
public void doing() {
int t = ++time;
System.out.println("第"+t+"次开始:"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+t+"次结束:"+System.currentTimeMillis());
}
};
}
public void runup() {
simpleDidadida.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
simpleDidadida.stop();
}
}
注:wait()和sleep()的区别;wait()属于Object类,调用wait()方法相当于释放了“锁”,此时其他线程可以继续使用,wait()和notofy()针对两个线程互斥;sleep()属于Thread类,调用sleep()方法并没有释放锁,此时其他线程不可以继续使用。由于在上述例子中只有一个主要线程,所以在这里用WAIT()或者用sleep()都是可以的,但是其他多线程的情况下,就需要考虑清楚了,个人认为用wait()比较合适。
下面来看一下最终的执行结果:
第1次开始:1585904659287
第2次开始:1585904659787
第1次结束:1585904660287
第3次开始:1585904660287
第4次开始:1585904660786
第2次结束:1585904660787
第3次结束:1585904661291
第5次开始:1585904661292
第4次结束:1585904661790
第6次开始:1585904661797
第7次开始:1585904662290
第5次结束:1585904662292
第8次开始:1585904662791
第6次结束:1585904662797
第7次结束:1585904663290
第9次开始:1585904663290
第8次结束:1585904663791
第10次开始:1585904663792
第9次结束:1585904664292
第10次结束:1585904664802
这里需要进一步说明的是,定时器只具有“定时”的功能,并不能确保在规定的时间内就一定能够完成你要做的事,定时器不管你有没有做完你的事,它还是继续进行定时,只要时间一到就“醒来”。上面的执行结果清楚的表现了每一次开始和结束的时间,可以看出某一次事件还没完成的时候,下一次事件就有可能开始,它的执行顺序并不是一定规律的。
以上是对定时器的简单实现,主要是理解定时器的工作原理以及功能。