【昕宝爸爸小模块】Java中Timer实现定时调度的原理(解析)

在这里插入图片描述

Java中Timer实现定时调度的原理

  • ✔️ 引言
  • ✔️JDK 中Timer类的定义
  • ✔️拓展知识仓
    • ✔️优缺点

✔️ 引言

Java中的Timer类是用于计划执行一项任务一次或重复固定延迟执行的简单工具。它使用一个名为TaskQueue的内部类来存储要执行的任务,这些任务被封装为TimerTask对象。

Timer实现定时调度的基本原理:

  1. 创建 Timer 对象:当你创建一个Timer对象时,它会实例化一个线程(不是守护线程),这个线程用于执行计划任务。
  2. 添加任务:你可以使用schedule或scheduleAtFixedRate方法向Timer添加任务。这些方法接受一个TimerTask对象和一个延迟时间(以及可选的重复间隔)。
  3. 内部存储:Timer内部使用一个优先级队列(具体是TaskQueue类)来存储要执行的任务。队列中的每个元素都是一个表示要执行的任务的TimerTask对象。队列根据任务的执行时间进行排序,以确保任务按照预定的时间顺序执行。
  4. 执行线程:Timer类的线程在后台运行,并定期检查任务队列。当线程发现队列中有任务到达其预定的执行时间时,它会从队列中取出该任务并执行它。
  5. 处理重复任务:对于需要重复执行的任务,Timer会重新计算下一个执行时间,并将任务重新放入队列中。这样,当任务的下一次执行时间到达时,线程会再次从队列中取出并执行它。
  6. 取消任务:你可以使用Timer.cancel()方法来取消所有已调度的任务,或者使用TimerTask.cancel()方法来取消单个任务。取消的任务将从队列中移除,并且不会再次调度。
  7. 注意事项:虽然Timer在简单场景下可以很好地工作,但它并不是最适合所有场景的定时任务解决方案。特别是,在需要更复杂的调度需求或在并发环境中,使用ScheduledExecutorService可能是更好的选择。

总而言之,Java中的Timer类通过内部使用一个优先级队列来存储和管理定时任务,并通过一个单独的线程来检查和执行这些任务,从而实现了定时调度功能。

✔️JDK 中Timer类的定义

Java中的Timer类是一个定时调度器,用于在指定的时间点执行任务。JDK 中Timer类的定义如下:

public class Timer {		/**	*	*     The timer task queue.This data structure is shared with the timer	*     thread. The timer produces tasks, via its various schedule calls,	*     and the timer thread consumes, executing timer tasks as appropriate,	*     and removing them from the queue when they're obsolete.	*/	private final TaskOueue queue = new TaskOueue() ;	/**	*     The timer thread	*/	private final TimerThread thread = new TimerThread(queue);}

以上就是 Timer 中最重要的两入成员变量:

1、TaskQueue: 一个任务队列,用于存储已计划的定时任务。任务队列按照任务的执行时间进行排序,确保最早执行的任务排在队列前面。在队列中的任务可能是一次性的,也可能是周期性的。

2、TimerThread : Timer 内部的后台线程,它负责扫描 TaskQueue 中的任务,检查任务的执行时间,然后在执行时间到达时执行任务的 run() 方法。TimerThread 是一个守护线程,因此当所有非守护线程完成时,它会随之终止。

任务的定时调度的核心代码就在TimerThread 中:

/***   @author xinbaobaba*/class TimerThread extends Thread {	//标志位	boolean newTasksMayBeScheduled = true;	/**	*    存储 TimerTask 的队列	*/	private TaskQueue queue ;		TimerThread(TaskQueue queue) {		this .queue = queue;	}	public void run() {		try {			mainLoop();		} finally {			synchronized (queue) {				newTasksMayBeScheduled = false;				queue.clear();			}		}	}	/**	*    主要的计时器循环。	*/	private void mainLoop() {		while (true) {			try {				TimerTask task;				boolean taskFired;				synchronized (queue) {					//等待队列变为非空					while (queue.isEmpty() && newTasksMayBeScheduled)					queue .wait() ;					if (queue.isEmpty())						// 队列为空,将永远保持为空; 线程终止						break;					//队列非空;查看第一个事件并执行相应操作					long currentTime, executionTime;					task = queue .getMin();					synchronized (task.lock) {						if (task.state == TimerTask.CANCELLED) {							queue .removeMin() ;							//无需执行任何操作,再次轮询队列							continue; 						}						currentTime = System.currentTimeMillis();						executionTime = task.nextExecutionTime;						if (taskFired = (executionTime <= currentTime)) {							if (task.period == 0) {// 非重复,移除								queue.removeMin);								task.state = TimerTask.EXECUTED;							} else {// 重复任务,重新安排								queue.rescheduleMin(									task.period <  ? currentTime   -   task.period : executionTime + task.period);							}						}					}					if (!taskFired)  // 任务尚未触发;等待					queue .wait(executionTime - currentTime);				}				if (taskFired) // 任务触发;运行它,不持有锁				task.run();			} catch (InterruptedException e) {			}		}	}}

可以看到,TimerThread的实际是在运行mainLoop方法,这个方法一进来就是一个while(true)的循环,他在循环中不断地从TaskQueue中取出第一个任务,然后判断他是否到达执行时间了,如果到了,就触发任务执行。否则就继续等一会再次执行。

不断地重复这个动作,从队列中取出第一个任务进行判断,执行。。。

这样只要有新的任务加入队列,就在队列中按照时间排队,然后唤醒timerThread重新检查队列进行执行就可以了。代码如下:

private void sched(TimerTask task, long time, long period) {	if (time < 0) {		throw new IllegalArgumentException("Illegal execution time.");	}	// Constrain value of period sufficiently to prevent numeric	// overflow while still being effectively infinitely large.	if (Math.abs(period) > (Long.MAX VALUE >> 1))		period >>= 1;	synchronized(queue) {		if (!thread.newTasksMayBeScheduled)			throw new IllegalstateException("Timer already cancelled.");					synchronized(task.lock) {			if (task.state != TimerTask.VIRGIN) 				throw new IllegalstateException("Task already scheduled or cancelled");			task.nextExecutionTime = time;			task.period = period;						task.state = TimerTask.SCHEDULED;		}		//新任务入队列		queue.add(task);		//唤醒任务		if (queue.getMin() == task)			queue .notify();	}}

✔️拓展知识仓

✔️优缺点

Timer 类用于实现定时任务,最大的好处就是他的实现非常简单,特别的轻量级,因为它是Java内置的,所以只需要简单调用就行了。

但是他并不是特别好的解决定时任务的方案,因为他存在以下问题:

1、Timer内部是单线程执行任务的,如果某个任务执行时间较长,会影响后续任务的执行

2、如果任务抛出未捕获异常,将导致整个 Timer 线程终止,影响其他任务的执行

3、Timer 无法提供高精度的定时任务。因为系统调度和任务执行时间的不确定性,可能导致任务执行的时间不准确

4、虽然可以使用 cancel 方法取消任务,但这仅仅是将任务标记为取消状态,仍然会在任务队列中占用位置,无法释放资源。这可能导致内存泄漏

5、当有大量任务时,Timer 的性能可能受到影响,因为它在每次扫描任务队列时都要进行时间比较

6、Timer执行任务完全基于JVM内存,一旦应用重启,那么队列中的任务就都没有了

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/ce5bcd636c.html