【Java进阶篇】Java中Timer实现定时调度的原理(解析)-LMLPHP


✔️ 引言


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

  1. 创建 Timer 对象:当你创建一个Timer对象时,它会实例化一个线程(不是守护线程),这个线程用于执行计划任务。
  2. 添加任务:你可以使用schedulescheduleAtFixedRate方法向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 中最重要的两入成员变量:



任务的定时调度的核心代码就在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内置的,所以只需要简单调用就行了。


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


01-04 05:32