Java计时器
用途
使用线程实现Java计时器,主要用于根据用户给定的时间,然后隔一段时间做一些事。例如隔一段时间刷新一些列表等。然而在Executor框架里,有一个Executor的实现类ScheduledThreadPoolExecutor类,可以在给定的延迟后运行命令,或者定期执行命令,比Timer更灵活,功能更强大。
实现过程
注意sleep();和wait();方法
- sleep()方法:调用后该线程睡眠给定的时间,但是没有释放锁,一直持有锁。
- wait()方法:调用后该线程进入阻塞队列,同时释放锁。当被唤醒后再一次重新争夺锁。
- 在这里用的是wait()方法,因为sleep()方法在睡眠的时候一直占用锁资源,也就是说,sleep睡眠时间没有到,其它线程就得不到锁,无法进入就绪态,得不到CPU的使用权。因此使用sleep()方法会浪费CPU的资源,所以这里使用wait()方法。
方案一
1.首先定义一个抽象方法,这方法由外面实例化该类的使用者完成。
2.然后启动一个线程,使用wait()方法将该线程阻塞指定的时间,然后的调用该抽象方法。
代码展示
方案二
1.首先定义一个抽象方法,这方法由外面实例化该类的使用者完成。
2. 然后启动两个线程,一个线程用于计时(主要是根据给定的时间阻塞线程,阻塞时间到后唤醒另一个完成具体工作的线程),一个线程用于完成用户指定的工作(该线程主要是调用那个待完成的抽象方法)。
代码展示
//因为篇幅问题,这里只展示核心代码,其余代码和方案二相同。
@Override
public void run() {
try {
while(goon) {
synchronized (lock) {
//阻塞dealyTime毫秒
lock.wait(dealyTime);
}
//当阻塞时间到线程被唤醒后直接调用抽象类完成用户指定的工作
work();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//main方法
public static void main(String[] args) {
new Didadida() {
@Override
public void work() {
int n = 0;
//这里增加了for循环,为了增加该方法的执行时间,只是为了验证
for (int i = 0; i < 1000000000; i++) {
n += i;
}
System.out.println(System.currentTimeMillis());
}
}.start();
}
运行结果
这里的输出结果是以毫秒为单位的,这段代码验证的是阻塞1秒的结果
//实现类
package com.mec.didadida.core;
public abstract class Didadida implements Runnable {
private Object lock;
private volatile boolean goon;
private long dealyTime;
//给定一个默认时间值
private static final int DEFULETIME = 1000;
public Didadida() {
this.lock = new Object();
this.dealyTime = DEFULETIME;
this.goon=false;
}
//由外面设置延迟时间
public Didadida setDealyTime(long dealyTime) {
this.dealyTime = dealyTime;
return this;
}
//给定一个抽象方法,有外面实例化该类的用户实现具体的完成工作
public abstract void work();
//启动线程
public void startThread() {
如果线程已经启动,直接结束方法的调用
if (goon) {
return;
}
goon = true;
//启动工作线程
new Thread(new Action(), "工作").start();
//启动定时线程
new Thread(this, "计时器").start();
}
//结束线程
public void stop() {
if (!goon) {
return;
}
goon = false;
}
@Override
public void run() {
try {
while(goon) {
synchronized (lock) {
//将该线程阻塞给定的时间,单位毫秒
lock.wait(dealyTime);
//唤醒工作线程
lock.notify();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//定义一个内部类,完成具体的工作
class Action implements Runnable {
@Override
public void run() {
try {
while(goon) {
synchronized (lock) {
//阻塞工作线程
lock.wait();
}
//调用抽象
work();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//主类
package com.mec.didadida.test;
import com.mec.didadida.core.Didadida;
public class DidaTest {
public static void main(String[] args) {
//实例化类并且完成该抽象类的抽象方法
new Didadida() {
@Override
public void work() {
int n = 0;
//这里增加了for循环,为了增加该方法的执行时间,只是为了验证
for (int i = 0; i < 1000000000; i++) {
n += i;
}
System.out.println(System.currentTimeMillis());
}
}. startThread(); //调用启动线程方法
}
}
运行结果
这是给定时间为1秒所输出的结果,结果是当前时间用毫秒表示。
总结
方案一:
- 该方法使用一个线程完成计时工作,当线程阻塞指定时间后直接执行由用户实现的抽象方法。
- 假如该线程被唤醒,进入就绪态,如果work()方法执行时间过长,该线程就会在work()方法执行完后才能循环回去执行wait()方法,重新阻塞该线程指定的时间,这样用户的定时就不准确了,真正的时间是:线程阻塞时间 + work()方法执行的时间,所以会有误差。
- 为了解决这个问题,提出了方案二的解决办法。
方案二:
- 该方案提供两个线程来解决问题,一个问题负责计时,一个线程负责完成工作。
- 首先,当Didadida线程阻塞时间到被唤醒后执行notify()方法,唤醒Action线程,然后循环回去继续执行wait()方法,阻塞指定的时间,等到时间到了在重复此操作。Action线程被唤醒后执行work()方法,该方法执行完后马上回去继续阻塞等待下一次被唤醒。
不足
上述两种方案都存在一个不足,那就是具体完成任务的work()方法不能太复杂,也就是执行时间不能过长,如果执行时间过长,都会对调用该方法的线程有所延迟,产生误差!