demo
以下是一个基本的计时器演示,它会在一定的时间间隔后执行指定的任务:
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
public static void main(String[] args) {
//创建定时器
Timer timer = new Timer();
// 创建一个定时任务
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("定时任务执行了!");
}
};
// 在1000毫秒后开始执行任务,然后每2000毫秒执行一次
timer.schedule(task, 1000, 2000);
}
}
在这个例子中,我们使用了java.util.Timer
和java.util.TimerTask
类来创建一个简单的定时器演示。您可以根据需要调整定时器的延迟和执行间隔。在上述代码中,任务会在启动后1秒开始执行,然后每隔2秒执行一次。
实现原理
定时器的实现原理涉及Java中的 java.util.Timer
类和 java.util.TimerTask
类。以下是它们的基本工作原理:
-
java.util.Timer
类: 这个类允许您创建定时任务,并安排它们在指定的时间点执行或以固定的时间间隔重复执行。它使用一个后台线程来调度任务的执行。 -
java.util.TimerTask
类: 这是一个抽象类,表示一个要执行的定时任务。您需要扩展这个类并实现其run()
方法,该方法包含实际任务的逻辑。
基本步骤是这样的:
-
创建一个
Timer
实例:通过实例化java.util.Timer
类来创建一个定时器对象。 -
创建一个
TimerTask
实例:创建一个继承自java.util.TimerTask
的类,并在其中实现run()
方法,该方法定义了您希望在定时器触发时执行的任务逻辑。 -
安排任务执行:使用定时器的
schedule()
或scheduleAtFixedRate()
方法,将创建的TimerTask
对象与执行时间或执行间隔一起传递给定时器。这将安排定时器在指定的时间点执行任务,或者以指定的时间间隔重复执行任务。 -
后台线程调度:定时器内部使用一个后台线程来调度任务的执行。它会在设定的时间点或时间间隔触发任务的
run()
方法。 -
任务执行:当定时器触发时,在后台线程中执行与
TimerTask
关联的run()
方法,从而执行您定义的任务逻辑。
需要注意的是,java.util.Timer
类在多线程环境中可能存在一些限制和性能问题。如果需要更高级的定时任务管理功能和更好的性能,您还可以考虑使用 java.util.concurrent.ScheduledExecutorService
接口提供的调度器。
这就是Java定时器的基本工作原理。通过使用这些类,您可以在指定的时间点执行任务,或者以固定的时间间隔重复执行任务,从而实现定时任务的管理和调度。
小结
其实就是三个核心类:
1、调度器Timer
添加自定义任务线程到任务队列
2、调度线程TimerThread
循环从任务队列出队任务执行
3、自定义任务线程TimerTask
自定义任务线程继承TimerTask
源码分析
入口
定时器Timer的入口:java.util.Timer#scheduleAtFixedRate(java.util.TimerTask, long, long)
/**
* 添加业务线程到队列
*
* Schedules the specified task for repeated <i>fixed-rate execution</i>,
* beginning after the specified delay. Subsequent executions take place
* at approximately regular intervals, separated by the specified period.
*
* <p>In fixed-rate execution, each execution is scheduled relative to the
* scheduled execution time of the initial execution. If an execution is
* delayed for any reason (such as garbage collection or other background
* activity), two or more executions will occur in rapid succession to
* "catch up." In the long run, the frequency of execution will be
* exactly the reciprocal of the specified period (assuming the system
* clock underlying <tt>Object.wait(long)</tt> is accurate).
*
* <p>Fixed-rate execution is appropriate for recurring activities that
* are sensitive to <i>absolute</i> time, such as ringing a chime every
* hour on the hour, or running scheduled maintenance every day at a
* particular time. It is also appropriate for recurring activities
* where the total time to perform a fixed number of executions is
* important, such as a countdown timer that ticks once every second for
* ten seconds. Finally, fixed-rate execution is appropriate for
* scheduling multiple repeating timer tasks that must remain synchronized
* with respect to one another.
*
* @param task task to be scheduled. 业务线程
* @param delay delay in milliseconds before task is to be executed. 第一次执行的延迟时间
* @param period time in milliseconds between successive task executions. 间隔时间
* @throws IllegalArgumentException if {@code delay < 0}, or
* {@code delay + System.currentTimeMillis() < 0}, or
* {@code period <= 0}
* @throws IllegalStateException if task was already scheduled or
* cancelled, timer was cancelled, or timer thread terminated.
* @throws NullPointerException if {@code task} is null
*/
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
//添加业务线程到队列
sched(task, System.currentTimeMillis()+delay, period);
}
添加业务线程到队列
/**
* Schedule the specified timer task for execution at the specified
* time with the specified period, in milliseconds. If period is
* positive, the task is scheduled for repeated execution; if period is
* zero, the task is scheduled for one-time execution. Time is specified
* in Date.getTime() format. This method checks timer state, task state,
* and initial execution time, but not period.
*
* @throws IllegalArgumentException if <tt>time</tt> is negative.
* @throws IllegalStateException if task was already scheduled or
* cancelled, timer was cancelled, or timer thread terminated.
* @throws NullPointerException if {@code task} is null
*/
/**
* 添加业务线程到队列
*
* @param task 业务线程
* @param time 延迟时间:延迟多久开始执行——只在第一次执行的时候才延迟 //initialDelay – the time to delay first executio
* @param period 间隔时间:每隔几秒,执行一次
* @author javaself
*/
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();
}
}
核心代码是这行
//先入队列
queue.add(task);
作用是,先把当前业务线程添加到队列。
那业务线程什么时候被执行呢?下面会讲。
业务线程在哪里被执行?
业务线程是在调度线程里被执行的。
调度线程的入口:java.util.TimerThread#run
public void run() {
try {
//循环执行业务线程
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
继续看循环方法:java.util.TimerThread#mainLoop
/**
* 调度线程:循环执行业务线程。
*
* 主要步骤是
* 1.从队列取业务线程
* 2.然后,执行业务线程
*
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait(); //如果队列为空,就等待
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
//获取时间最早的业务线程
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) { //如果业务线程状态被取消,那么删除业务线程
queue.removeMin();
continue; // No action required, poll queue again
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) { //判断业务线程是否到了触发时间
if (task.period == 0) { //非重复执行任务,会删除任务 // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { //重复执行任务,不会删除任务 // Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime); //如果业务线程还未触发(因为时间没到),也等待
}
if (taskFired) // Task fired; run it, holding no locks
//如果时间到了,就执行任务线程
task.run(); //
} catch(InterruptedException e) {
}
}
}
主要步骤看代码注释,其实就是两步:
- 从队列里读数据
//获取时间最早的业务线程
task = queue.getMin();
- 然后,消费数据
if (taskFired) // Task fired; run it, holding no locks
//如果时间到了,就执行任务线程
task.run(); //
这里的数据,指的就是业务线程。
读数据,就是指,从队列获取业务线程。
消费数据,就是指,执行业务线程。
而且,要注意,这个过程是循环的,即一直在循环处理,正常情况下,永不结束。
如何知道定时任务是否到期?
其实就是比较时间:比较到期时间和当前时间。
队列数据
队列数据
/**
* Our Timer's queue. We store this reference in preference to
* a reference to the Timer so the reference graph remains acyclic.
* Otherwise, the Timer would never be garbage-collected and this
* thread would never go away.
*/
private TaskQueue queue;
TaskQueue源码
/**
* 定时任务线程集合:准确的说是队列,而且是优先级队列。
*
* 基于二叉堆实现。
*
* This class represents a timer task queue: a priority queue of TimerTasks,
* ordered on nextExecutionTime. Each Timer object has one of these, which it
* shares with its TimerThread. Internally this class uses a heap, which
* offers log(n) performance for the add, removeMin and rescheduleMin
* operations, and constant time performance for the getMin operation.
*/
class TaskQueue {
/**
* Priority queue represented as a balanced binary heap: the two children
* of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is
* ordered on the nextExecutionTime field: The TimerTask with the lowest
* nextExecutionTime is in queue[1] (assuming the queue is nonempty). For
* each node n in the heap, and each descendant of n, d,
* n.nextExecutionTime <= d.nextExecutionTime.
*/
private TimerTask[] queue = new TimerTask[128]; //优先级队列,基于二叉堆实现
其实是优先级队列
那怎么等待和唤醒?
其实就是queue.wait()/notify()
如果时间没到,就wait
如果时间到了,就notify,即执行任务
那调度线程本身在哪里被执行?
创建定时器的时候,就已经启动执行。后面如果有任务入队之后,就会一直循环从队列出队任务执行。
/**
* Creates a new timer whose associated thread has the specified name.
* The associated thread does <i>not</i>
* {@linkplain Thread#setDaemon run as a daemon}.
*
* @param name the name of the associated thread
* @throws NullPointerException if {@code name} is null
* @since 1.5
*/
public Timer(String name) {
thread.setName(name);
//启动调度线程
thread.start();
}
生产者消费者模式
定时器的主要步骤有两步
- 先把数据写到队列
- 然后,再消费数据
这两步是解耦的,是分开的,是独立的,不是按顺序执行的——说白了,就是典型的生产者和消费者模式。生产者和消费者,互不影响。