didadida——定时器
在软硬件两个方面,定时器的应用都很广泛,这里主要讲在软件方面的应用,及设计一个定时器工具。
应用场景
一、数据同步。
客户端需要每隔一段时间就向服务器取数据,比如增加或者修改一些记录,而这些记录应该被及时的在服务器数据库同步,不然就会读到"脏数据"。
保持数据同步可以考虑两种方式。
1.服务器向各客户端推送最新的数据或者说更改以后的数据,但是这种方式服务器压力会很大,不建议采用。
2.客户端主动向服务器要数据,将客户端上一次取数据时间的数据与服务器当前时间的数据进行对比,如果数据没变化,则什么也不做;如果有变化,则服务器将变化后的数据传给客户端,并更新时间。
二、软负载均衡。
当客户端请求服务时,如果有多个服务器,那么客户端可以通过定时器向不同的服务器发送请求,记录下之间的往返时间差,由此来判断哪个服务器的压力更小。往返时间差越小,则说明对应的服务器压力小一些,则将请求交给该台服务器处理,需要说明的是这种负载均衡只是相对的。
定时器功能
在用户设定或者说规定时间内,执行一次用户想做的操作。
关键点
- synchronized和volatile关键字的使用
- 线程同步
- 接口
- 对象锁
详细设计
1. 对于用户想执行的操作,具体的操作应该用户来实现,因此这里我用接口来规范用户的行为。
IDidaDidaAction接口具体内容如下。
package com.mec.timer.didadida;
public interface IDidaDidaAction {
void doSomething();
}
2.具体的DidaDida定时器内容如下。
package com.mec.timer.didadida;
public class DidaDida implements Runnable {
public static final long DEFAULT_DELAY_TIME = 1000;
private long delayTime;
private Object lock;
private volatile boolean goon;
private IDidaDidaAction action;
public DidaDida() {
this(DEFAULT_DELAY_TIME);
}
public DidaDida(long delayTime) {
this(delayTime, null);
}
public DidaDida(long delayTime, IDidaDidaAction action) {
this.lock = new Object();
this.delayTime = delayTime;
this.action = action;
}
public void setAction(IDidaDidaAction action) {
this.action = action;
}
public void setDelayTime(long delayTime) {
this.delayTime = delayTime;
}
public void start() {
if (action == null) {
System.out.println("无事可做!");
return;
}
if (goon) {
return;
}
goon = true;
new Thread(new TimeWorker(), "TimeWork").start();
new Thread(this, "DidaDida").start();
}
public void stop() {
if (action == null) {
System.out.println("没做任何事!");
return;
}
if (goon == false) {
System.out.println("时钟已经停止计时!");
return;
}
goon = false;
}
@Override
public void run() {
while (goon) {
// 这里才是真正的定时器,而且,它只定时!
synchronized (lock) {
try {
//定时线程将自己阻塞delayTime后,JVM将其唤醒。
lock.wait(delayTime);
// 这里应该唤醒一个线程,这个线程专心负责执行Action!,如果阻塞队列为空的话,则什么也不做。
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TimeWorker implements Runnable {
public TimeWorker() {
}
@Override
public void run() {
while (goon) {
try {
synchronized (lock) {
lock.wait();
}
if (!goon) {
return;
}
// 此线程被唤醒,则执行此方法,执行完,又自身阻塞
action.doSomething();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
尤其需要注意的是,这里的定时器线程只定时唤醒action执行线程,如果执行线程执行的任务耗时甚至超过定时器规定的时间,不影响定时线程的执行,定时线程还是会去定时唤醒执行线程(如果这个时候阻塞队列为空,则无操作),所以最后执行完任务的时间必然是定时时间的整数倍。
比如,我是一个管家,你是一个佣工,我叫你去扫地。每隔一段时间我就会来叫你扫地,而不管你是否正在扫地。
测试代码
测试时,尽量关闭不用的程序。
package com.mec.timer.didadida.test;
import com.mec.timer.didadida.DidaDida;
import com.mec.timer.didadida.IDidaDidaAction;
public class Test {
public static void main(String[] args) {
DidaDida dida = new DidaDida(200L, new IDidaDidaAction() {
@Override
public void doSomething() {
System.out.println("********\n"+System.currentTimeMillis()+"\n***********");
for (int i = 0; i < 10000; i++) {
System.out.print(i);
}
}
});
dida.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dida.stop();
System.out.println("停止!");
}
}
用cmd命令行通过输出重定向试验,结果截图如下图:
定时时间为200ms,误差在1ms左右。
反思
有人说,Thread.sleep()似乎也可以实现类似功能,为什么不用它呢?因为Thread.sleep()会空耗CPU时间,造成资源的浪费。
从代码的执行效率来讲,用C语言,汇编语言编写的定时器会更精确一些。
资源地址:https://github.com/dx99606707/depository/blob/master/滴答滴答定时器.rar