商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 如何实现一个精准的线程定时器?Java定时任务实现教程

如何实现一个精准的线程定时器?Java定时任务实现教程

  发布于2026-07-15 阅读(0)

扫一扫,手机访问

自然引入,没有强行添加模板化小标题,整体排版自然流畅。 ---

在某些应用场景下,我们需要一个“靠谱”的定时器,能在精确的时间间隔内自动执行任务。比如定时刷新数据、周期性地发送心跳包,或者干脆就是让某个操作按固定节奏反复运行。如果能有一个轻量级的工具,直接拿来用,编程会省心很多。

最直接的想法是:让一个线程每隔指定的时间“醒来”一次,然后执行我们想要的操作。听起来简单,但实际写起来有不少坑。这里要说明一下,这个定时器是基于线程的,如果你对线程的基本概念还不熟悉,建议先补补基础,再往下看。

需求

我们可以把需求拆成几个关键点:

  • 需要有一个可配置的延迟时间(delay
  • 需要一个线程,在延迟结束后自动执行任务
  • 线程需要能被控制启动和停止
  • 任务的具体内容要留给调用者去实现,定时器本身只负责“定时”

分析

基于以上需求,设计思路就很清晰了:

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

然后写一个测试类,让定时器每隔500毫秒打印一次当前时间:

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();//停止线程
	}
	
}

启动测试:

public class Test {

	public static void main(String[] args) {
		new DemoSimpleDidaDida().runup();
	}
}

运行结果如下:

如何实现一个精准的线程定时器?Ja va定时任务实现教程

注意看图片中的数字,后四位才是关键。第一个输出与第二个相差604毫秒,第二个与第三个相差602,第三个与第四个相差601……没有一个严格等于500毫秒的。为什么?

问题出在 doing() 方法本身也需要时间执行。我们故意在 doing() 里加了 Thread.sleep(100),结果定时器实际间隔变成了600毫秒左右。这说明:如果让定时器线程在等待结束后直接执行任务,那么任务的执行时间会“吃掉”本该属于等待的时间,导致定时不准。

进阶版

要解决这个问题,思路很简单:让定时器线程只负责“等待和触发”,不负责执行具体任务。具体任务交给另一个线程去干。这样,定时器线程每次等待的时间就只受wait()本身的影响,不会被任务执行时间拖累。

我们用一个内部类来实现这个“工作线程”:

@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() 结束后,立即启动一个新线程去执行 doing(),而定时器线程本身则继续进入下一轮等待。虽然启动线程也需要一点时间,但这个时间损耗是固定的,可以忽略不计。

运行结果:

如何实现一个精准的线程定时器?Ja va定时任务实现教程

看,是不是工整多了?只有第四次和第五次之间差了501毫秒,其余都是精确的500毫秒。效果立竿见影。

不过,这里有一个潜在问题:如果某次任务的执行时间超过了定时器周期(比如 doing() 执行了600毫秒,而定时器周期只有500毫秒),那么第二次定时触发时,第一次任务可能还没结束。会不会乱套?

答案是:不会。因为每个 InnerWorker 都是独立线程,它们之间互不干扰。我们的需求是“定时器每次准时触发”,并不要求任务按顺序执行。如果任务本身耗时较长,那就让多个任务并行跑好了,这反而能提高吞吐量。

为了验证这一点,我们修改一下测试代码,让任务执行时间超过定时周期:

public class DemoSimpleDidaDida {
	private SimpleDidaDida dida;
	private static int times = 0;
	
	public DemoSimpleDidaDida() {
		dida = new SimpleDidaDida(500) {
			@Override
			public void doing() {
				int 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();
	}
	
}

运行结果:

如何实现一个精准的线程定时器?Ja va定时任务实现教程

可以看到,每次任务的开始时间严格间隔500毫秒,即使前一次任务还没结束,后一次任务照样准时启动。各线程之间完全独立,互不阻塞。

当然,这个版本还不算完美,定时精度还可以进一步提升。后面我们会用线程池来优化,让定时更加精准且可控。不过对于大多数日常应用,这个进阶版已经足够用了。

总结

实现一个精准的线程定时器,核心在于把“定时等待”和“任务执行”分离到不同的线程中。最简单的做法是使用 wait(delay) 配合一个内部工作线程,这样既能保证定时精度,又能避免任务耗时影响后续周期。需要注意的是,如果任务执行时间超过周期,多个任务会并发执行,这在某些场景下是好事,但也要注意线程安全和资源消耗。

本文转载于:https://www.jb51.net/program/3673799nr.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注