java定时器之Timer使用与原理分析[通俗易懂]

java定时器之Timer使用与原理分析[通俗易懂]

大家好,又见面了,我是你们的朋友全栈君。

Timer和TimerTask

Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。

TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。

【使用举例】

【schedule(TimerTask task, long delay) 延迟 delay 毫秒 执行】

代码语言:javascript代码运行次数:0运行复制public static void main(String[] args) {

for (int i = 0; i < 10; ++i) {

new Timer("timer - " + i).schedule(new TimerTask() {

@Override

public void run() {

println(Thread.currentThread().getName() + " run ");

}

}, 1000);

}

}【schedule(TimerTask task, Date time) 特定时间执行】

代码语言:javascript代码运行次数:0运行复制public static void main(String[] args) {

for (int i = 0; i < 10; ++i) {

new Timer("timer - " + i).schedule(new TimerTask() {

@Override

public void run() {

println(Thread.currentThread().getName() + " run ");

}

}, new Date(System.currentTimeMillis() + 2000));

}

}【schedule(TimerTask task, long delay, long period) 延迟 delay 执行并每隔period 执行一次】

代码语言:javascript代码运行次数:0运行复制public static void main(String[] args) {

for (int i = 0; i < 10; ++i) {

new Timer("timer - " + i).schedule(new TimerTask() {

@Override

public void run() {

println(Thread.currentThread().getName() + " run ");

}

}, 2000 , 3000);

}

}【原理分析】

timer底层是把一个个任务放在一个TaskQueue中,TaskQueue是以平衡二进制堆表示的优先级队列,他是通过nextExecutionTime进行优先级排序的,距离下次执行时间越短优先级越高,通过getMin()获得queue[1]

并且出队的时候通过synchronized保证线程安全,延迟执行和特定时间执行的底层实现类似,源码如下:

代码语言:javascript代码运行次数:0运行复制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();

}

}我们主要来看下周期性调度通过什么方式实现的,我们直接来分析源码如下:

代码语言:javascript代码运行次数:0运行复制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)) {

// 到这里是延迟执行和特定时间点执行已经结束了,状态标记为EXECUTED,周期性执行继续往下走

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

}

}

}

// 如果任务执行时间大于当前时间说明任务还没点,继续等,否则执行run代码块

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) {

}

}

}

}这种定时器适合单点或者多台同时执行互不影响的场景

【缺点】

1、首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。

2、其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为

3、Timer在执行定时任务时只会创建一个线程任务,如果存在多个线程,若其中某个线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,会导致下一个任务执行时间滞后

这些缺点可以通过ScheduledExecutorService来代替

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/156801.html原文链接:https://javaforall.cn

相关推荐

怎么防止仓鼠打架,养殖技巧与注意事项分享
外围365bet网址

怎么防止仓鼠打架,养殖技巧与注意事项分享

📅 07-16 👁️ 4822
电信流量卡能用多久,电信卡办理多久可以使用流量
世界杯氛围逐渐拉满,万达酒店开启官方联名主题营销
365足球打水封号还严重嘛

世界杯氛围逐渐拉满,万达酒店开启官方联名主题营销

📅 06-30 👁️ 5248