需求:
在某些应用场合,需要定时地完成一些操作,希望能有一个“精准”的定时的工具,方便编程。
应该有一个线程,这个线程每经过一段时间(这个时间可以指定),就“醒来”,并执行外部要求完成的操作。
注:该定时器的编写是基于线程的,如对线程知识毫无了解,请绕道而行。
分析:
1.作为一个定时器,需要定义一个时延,即要求等待的时间delay
;
2.需要用线程来实现该定时器,便需要一个变量来控制线程goon
;
3.既然存在线程,必然要给两个方法,开启线程start()
,关闭线程stop()
;
4.定时器要应用在某些场合来做一些其他事情,那么就需要一个方法来负责做某些事,但是由于现在无法获悉未来要做什么,所以给一个抽象方法doing()
,未来由其他类来实现该方法。
具体实现:
最简陋版的定时器:
public abstract class SimpleDidaDida implements Runnable {
public static final long DEFAULT_DELAY=1000;//默认时延
private long delay;//时延
private volatile boolean goon;//便于控制线程
private Object lock;//确保可以精准等待,给一个对象锁,wait()等基于该锁
//初始化时允许用户指定等待时间,若不指定,则使用默认时延
public SimpleDidaDida() {
this(DEFAULT_DELAY);//调用单参构造
}
public SimpleDidaDida(long delay) {
this.lock = new Object();//初始化对象锁
this.delay = delay;
}
//具体完成的事情不由该类实现
public abstract void doing();
//启动线程
public void start() {
if(this.goon == true) {
return;
}
this.goon = true;
new Thread(this).start();
}
//停止线程,当线程等待执行完doing后,goon==false时
public void stop() {
if(this.goon == false) {
return;
}
this.goon = false;//说明线程运行完毕
}
@Override
public void run() {
while(goon) {
synchronized(lock) {
try {
//当前线程暂停执行,即处于等待状态,并释放对象锁;
//当超过delay或执行notify/notifyAll()方法,该线程会被唤醒
lock.wait(delay);
doing();//调用抽象方法
} catch (InterruptedException e) {
goon = false;
}
}
实现doing()
方法的类:
public class DemoSimpleDidaDida {
private SimpleDidaDida dida;
private static int times = 0;
public DemoSimpleDidaDida() {
//此处500,表示500毫秒运行一次
dida = new SimpleDidaDida(500) {
@Override
public void doing() {
System.out.println(System.currentTimeMillis());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
public void runup() {
dida.start();//启动线程
try {
Thread.sleep(5000);//此处要求线程运行5秒后停止,5000单位是毫秒(ms)
} catch (InterruptedException e) {
}
dida.stop();//停止线程
}
}
实例化DemoSimpleDidaDida
类:
public class Test {
public static void main(String[] args) {
new DemoSimpleDidaDida().runup();
}
}
运行结果:
上述图片中的数字单位都是毫秒,只需要注意后四位即可,第二个数字与第一个数字相差604,第三个数字与第二个数字相差602,第四个数字与第三个数字相差601,之后的数字相差也并不是500ms.
为什么会这样呢?不妨考虑一下,在运行wait(delay)
方法之后,运行了doing()
方法,我们在doing()
方法中添加了Thread.sleep(100)
,而运行结果显示相差时间都在600ms左右,这足以说明结果不准确都是因为doing()
方法本身就是需要花费时间的。这就导致了定时器并不准确。
进阶版:
为了增加定时器的准确性,不能再wait()
方法执行后去执行doing()
方法。为了保证该线程的纯粹性,我们选择使用线程,在wait()
之后再启动一个线程,来专门执行doing()
,而这个线程的实现我选择内部类来实现。
为了避免冗余,下面只列出被改动部分的代码
@Override
public void run() {
while(goon) {
synchronized(lock) {
try {
//当前线程暂停执行,即处于等待状态,并释放对象锁;
//当超过delay或执行notify/notifyAll()方法,该线程会被唤醒
lock.wait(delay);
new InnerWorker();//内部类启动线程
} catch (InterruptedException e) {
goon = false;
}
}
}
}
//该内部类主要负责启动一个线程,让该线程去执行doing().
private class InnerWorker implements Runnable{
public InnerWorker() {
new Thread(this).start();
}
@Override
public void run() {
doing();
}
}
在上述程序中,wait()
执行完后执行了内部类,虽然还是有时间的损耗,但是因为作用是启动线程,所以每次损耗的时间都是一个固定的值,方便控制。
运行结果:
是不是相对工整了很多,只有第四次和第五次相差501,其他都只相差500ms.
但是该模式下有一个问题:如果线程第二次开始执行,第一次doing()
还未执行完,该如何?
答:这种情况其实是建立在doing()
的运行时间超过线程的休眠时间之上的。但其实并没有什么影响,因为每一个线程都是独立运行的,我们的需求是做一个定时器,要求每次线程定时启动即可,不要求doing()
按顺序执行。
为了验证该问题,我对代码进行微小的修改,来证明这个问题.输出时间时,加上这是第几次运行的。
public class DemoSimpleDidaDida {
private SimpleDidaDida dida;
private static int times = 0;
public DemoSimpleDidaDida() {
dida = new SimpleDidaDida(500) {
@Override
public void doing() {
nt time = ++times;
System.out.println("第" + time+ "次开始:" + System.currentTimeMillis());
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + time+ "次结束:" + System.currentTimeMillis());
}
}
};
}
public void runup() {
dida.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
dida.stop();
}
}
如图所示,其实每次执行的线程之间都是独立运行,不会影响其他线程的运行结果。
目前为止,定时器还不是很完善,定时结果也不是非常精准,后续会有更加精准的方案,届时会使用线程池来完成,定时将更加精准。