Java多线程实现定时器

需求:

在某些应用场合,需要定时地完成一些操作,希望能有一个“精准”的定时的工具,方便编程。
应该有一个线程,这个线程每经过一段时间(这个时间可以指定),就“醒来”,并执行外部要求完成的操作。

注:该定时器的编写是基于线程的,如对线程知识毫无了解,请绕道而行。

分析:

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();
	}
	
}

在这里插入图片描述
如图所示,其实每次执行的线程之间都是独立运行,不会影响其他线程的运行结果。


目前为止,定时器还不是很完善,定时结果也不是非常精准,后续会有更加精准的方案,届时会使用线程池来完成,定时将更加精准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值