1. Timer简单使用
简单使用:
public class TimerTest {
public static void main(String[] args) throws InterruptedException {
// 创建Timer对象
Timer timer = new Timer();
// 延迟1秒执行任务,只执行一次。
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 1000);
Thread.sleep(2000);
timer.cancel(); // 取消任务执行,如果没有调用cancel,timer线程不会结束。
}
}
TimerTask接口继承了Runnable,重写里面的run方法就可以自定义任务。
2. Timer
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 TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
private final TimerThread thread = new TimerThread(queue);
TaskQueue用来存储提交的定时任务的队列;TimerThread是Timer的内部类,继承了Thread类。
TaskQueue的底层实现是数组,它是一个优先队列。优先级参考下一次执行时间(每一个TimerTask下一次的执行时间),越快该执行的任务就会排的越靠前。
private TimerTask[] queue = new TimerTask[128];
TimerThread是一个线程,当Timer创建时,该线程就被启动:
public Timer(String name) {
thread.setName(name);
thread.start();
}
3. 配置定时任务
主要用Timer中的schedule方法来配置不同的定时任务。
- schedule(TimerTask task, long delay):在delay(毫秒)延迟后,执行任务task。只执行一次。
- schedule(TimerTask task, Date time):在指定的时间点(time)执行task。如果时间点是过去某个时刻,那么该任务将被立即执行。
- schedule(TimerTask task, long delay, long period):在当前时间delay(毫秒)延迟后执行task,然后每隔period(毫秒)执行一次
- schedule(TimerTask task, Date firstTime, long period):在指定时间firstTime执行第一次task,然后每隔period(毫秒)执行一次。
- scheduleAtFixedRate(TimerTask task, long delay, long period):在delay延迟后执行task,每隔period执行一次
- scheduleAtFixedRate(TimerTask task, Date firstTime, long period):在指定时间执行task,每隔period执行一次
3.1 schedule与scheduleAtFixedRate的区别
3.1.1 delay参数的方法
schedule(TimerTask task, long delay, long period)
与 scheduleAtFixedRate(TimerTask task, long delay, long period)
的区别:
两者都是在当前时间的delay延迟后,执行第一次任务,并且之后每隔period执行一次。
区别在于:当任务的执行时间 > 任务间间隔时间(period)时,
- schedule方法会阻塞,直到上一个任务执行完毕后,再执行下一个任务;
- scheduleAtFixedRate方法不会阻塞,上一个任务即使未执行完毕,只要间隔时间足够period,就执行下一个任务。
在演示前,需要搞明白两个时间变量:
- 系统时间(System.currentTimeMillis()或new Date()):正常流逝的时间。
- 任务被执行的时间(TimerTask中的scheduledExecutionTime()方法):按照指定参数,任务应该在哪个时间点被执行。
存在这一种情况:由于Timer是单线程执行任务,当任务的执行时间超过指定的任务间隔时间时,scheduleExecutionTime()方法的时间会早于系统时间。
示例:schedule
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
timer.schedule(new TimerTask() {
@Override
public void run() {
// 我们在任务中分别打印“任务被执行时间”和当前系统时间
System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
System.out.println("new Date() : " + dateFormat.format(new Date()));
try {
// 阻塞6秒,超过了任务间隔4秒
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 2000, 4000); // 指定任务执行间隔4秒
}
打印结果:
scheuledExecutionTime : 2020-08-23 13:49:33
new Date() : 2020-08-23 13:49:33
scheuledExecutionTime : 2020-08-23 13:49:39
new Date() : 2020-08-23 13:49:39
scheuledExecutionTime : 2020-08-23 13:49:45
new Date() : 2020-08-23 13:49:45
scheuledExecutionTime : 2020-08-23 13:49:51
new Date() : 2020-08-23 13:49:51
scheuledExecutionTime : 2020-08-23 13:49:57
new Date() : 2020-08-23 13:49:57
可以看到,任务执行时间(scheduleExecutionTime)和系统时间的间隔都是6秒。
任务执行的实际间隔是6秒,也就是上一个任务结束后,下一个任务才执行,并未按照设定的4秒间隔。 在schedule方法中,scheduleExecutionTime获取的是任务实际执行时的时间点。
示例:scheduleAtFixedRate
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
System.out.println("new Date() : " + dateFormat.format(new Date()));
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 2000, 4000);
}
打印结果:
scheuledExecutionTime : 2020-08-23 14:31:54
new Date() : 2020-08-23 14:31:54
scheuledExecutionTime : 2020-08-23 14:31:58
new Date() : 2020-08-23 14:32:00
scheuledExecutionTime : 2020-08-23 14:32:02
new Date() : 2020-08-23 14:32:06
scheuledExecutionTime : 2020-08-23 14:32:06
new Date() : 2020-08-23 14:32:12
scheuledExecutionTime : 2020-08-23 14:32:10
new Date() : 2020-08-23 14:32:18
可以看到:任务被执行的时间(scheduleExecutionTime)的间隔是4秒,而打印的系统时间间隔是6秒。也就是说,任务的执行时间间隔按照设定的4秒进行,并未因上一个任务的阻塞而导致间隔延长。
但是注意:“scheuledExecutionTime”和“new Date() ”两句话都是同时打印的,都是在一个task开始时被打印。
在每一次任务执行时,下一次任务的scheuledExecutionTime()都会被计算好,因此,在scheduleAtFixedRate方法中,scheduleExecutionTime()中获取的是按照指定时间间隔任务应该被执行的时间点 ,但任务实际被执行的时间明显晚于该时间点。
3.1.2 firstTime参数的方法
schedule(TimerTask task, Date firstTime, long period)
与 scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
的区别:
这两个方法的区别主要在于:
当firstTime早于当前系统时间时,执行的情况不同。
- schedule:会立即执行task一次,之后每隔period执行一次;
- scheduleAtFixedRate:会将从firstTime到当前系统时间(System.currentTimeMillis())之间应该执行的任务一次性执行完毕,然后继续按照period间隔执行。
示例:schedule
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 当前时间
System.out.printf("=============当前时间: %s=================
", dateFormat.format(new Date()));
// 从当前时间的20秒前开始执行任务
Date startDate = Date.from(Instant.now().minusSeconds(20));
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
}
}, startDate, 4000); // 每隔4秒执行一次
}
打印结果:
=============当前时间: 2020-08-23 14:45:05=================
scheuledExecutionTime : 2020-08-23 14:45:05
scheuledExecutionTime : 2020-08-23 14:45:09
scheuledExecutionTime : 2020-08-23 14:45:13
scheuledExecutionTime : 2020-08-23 14:45:17
scheuledExecutionTime : 2020-08-23 14:45:21
可以看到,任务是从当前时间开始执行的,并未从startDate这个时间点开始。
示例:scheduleAtFixedRate
public static void main(String[] args) throws InterruptedException {
Timer timer = new Timer();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 当前时间
System.out.printf("=============当前时间: %s=================
", dateFormat.format(new Date()));
// 从当前时间的20秒前开始执行任务
Date startDate = Date.from(Instant.now().minusSeconds(20));
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("scheuledExecutionTime : " + dateFormat.format(new Date(this.scheduledExecutionTime())));
}
}, startDate, 4000);
}
打印结果:
=============当前时间: 2020-08-23 14:47:30=================
scheuledExecutionTime : 2020-08-23 14:47:10
scheuledExecutionTime : 2020-08-23 14:47:14
scheuledExecutionTime : 2020-08-23 14:47:18
scheuledExecutionTime : 2020-08-23 14:47:22
scheuledExecutionTime : 2020-08-23 14:47:26
scheuledExecutionTime : 2020-08-23 14:47:30
scheuledExecutionTime : 2020-08-23 14:47:34
scheuledExecutionTime : 2020-08-23 14:47:38
scheuledExecutionTime : 2020-08-23 14:47:42
可以看到:scheduleAtFixedRate方法将从开始时间(startDate 14:47:10)到当前时间之间(new Date() 14:47:30)之间的所有任务都执行一遍(这些任务都是同时执行,一次性输出的),然后从当前时间起继续以4秒为间隔往下执行。
4. 底层实现
每一个schedule方法的底层都是调用了sched方法。
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();
}
}
该方法接收3个参数:
- task:执行的任务;
- time:任务下一次要执行的时间点;
- period:执行任务的时间间隔。
当我们构造 Timer 实例的时候,就会启动该线程,该线程会在一个死循环中尝试从任务队列上获取任务,如果成功获取就执行该任务并在执行结束之后做一个判断。
如果 period 值为零,则说明这是一次普通任务,执行结束后将从队列首部移除该任务。
如果 period 为负值,则说明这是一次固定延时的任务,修改它下次执行时间 nextExecutionTime 为当前时间减去 period,重构任务队列。
如果 period 为正数,则说明这是一次固定频率的任务,修改它下次执行时间为 上次执行时间加上 period,并重构任务队列。
5. Timer的不足
Timer是单线程的,不管任务多少,仅有一个工作线程;
限于单线程,如果第一个任务逻辑上死循环了,后续的任务一个都得不到执行。
依然是由于单线程,任一任务抛出异常后,整个 Timer 就会结束,后续任务全部都无法执行。