在平时的开发中,我们常常遇到一些这样的需求,比如一天打一个报表,一周打一个报表,一月打一个报表,每天把数据库里几年没登录的用户删除掉等等,我们往往会利用计时器来完成这些业务,所谓的计时器就是能够根据准确时间来完成业务的调用。
首先计时器应该是一个线程,它独立于业务,且尽量开销小,因为它存活的时间是可能非常久的。
其次业务函数调用的时间不算在计时器计时的时间内,因此调用业务函数应该也是一个线程。
频繁的切换上下文,或者创建消亡线程会非常影响时间的准确性,所以我们采用等待/通知模板完成。
一个系统中可能需要多个计时器,需要考虑到锁的应用。
那么我们的总体思想如下,单独计时的线程我们叫didadida,计时时间到了要去调用方法的线程我们叫didaworker;
package com.hzy.dadidida;
public abstract class DidaDida implements Runnable{
private volatile Boolean goon = false;
private int DefaultTime = 1000;
private int waitTime = 0;
private volatile Object lock;
public void startDida() {
//启动线程开始计时调用,在系统中每一个计时器线程都有一个单独的锁,互不影响。
lock = new Object();
goon = true;
DoSomethingWorker dsw = new DoSomethingWorker();
new Thread(dsw,"didaworkerThread").start();
new Thread(this,"didaThread").start();
}
public void setWaitTime(int time) {
this.waitTime = time;
}
/**
* 计时器启动之前
*/
public abstract void onDidaBegin();
/**
* 计时器完成之后
*/
public abstract void onDidaAfter();
/**
* 计时时间到而产生的动作
*/
public abstract void itTimeToDoSomething();
@Override
public void run() {
if(waitTime==0) {
waitTime = DefaultTime;
}
onDidaBegin();
while(goon) {
synchronized (lock) {
try {
lock.wait(waitTime);
//此处应该唤醒的线程来完成时间到的动作
lock.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
onDidaAfter();
}
public void stopDida() {
goon = false;
}
public class DoSomethingWorker implements Runnable{
public void run() {
while(goon) {
synchronized (lock) {
try {
//等待计时器线程的唤醒
lock.wait();
if(goon) {
itTimeToDoSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
上面这个就是使用了等待通知模板,自始至终我们都只有两个线程,减少了线程创建和销毁的开销。
package com.hzy.dadidida;
//自己写的一个工具,只是输出时前面加上时间
import com.mec.util.CurrentTime;
public class DidaMutex extends DidaDida{
private String name;
private int count;
public void start(String name) {
this.name = name;
count = 0;
startDida();
}
public void stop() {
stopDida();
}
@Override
public void onDidaBegin() {
//CurrentTime.ogMessage是自己写的一个工具,只是输出时前面加上时间
System.out.println(CurrentTime.logMessage("dida开始工作"));
}
@Override
public void onDidaAfter() {
System.out.println(CurrentTime.logMessage("dida线程结束"));
}
@Override
public void itTimeToDoSomething() {
count++;
System.out.println(CurrentTime.logMessage("dida线程"+name+"在第"+count+"次工作"));
}
}
提供给使用者让其自己写自己需要调用的方法。
做个测试:
public class Test {
public static void main(String[] args) {
DidaMutex dm1 = new DidaMutex();
dm1.start("1");
DidaMutex dm2 = new DidaMutex();
dm2.start("2");
}
}
每调用几次会有毫秒级别的误差,而且两个线程之间互不影响。
为啥会有毫秒级别的误差呢,原因是我们的线程唤醒也是要耗费时间的啊,我们打输出语句也是耗费时间的,但毫秒级别的误差我们还是能够接受的,如何能够让线程把时间卡的非常准确小编还没有接触到。感谢您的阅读,再会。
指导老师:微易码