我是靠谱客的博主 爱笑夕阳,最近开发中收集的这篇文章主要介绍定时器Timer,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

#定时器Timer

为什么要使用定时器呢?

比如说一个web应用,如果这个应用规模很大,那它的日志数据是不是很多。如果一直存下来服务器的存储量怕是不行吧,需要隔一段时间删除,那么就需要一个线程每隔一段时间去删除日志数据。

直接来个程序:

import java.util.Timer;
import java.util.TimerTask;

public class TestTimer {
public static void main(String[] args) {
    Timer timer=new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("我执行了"+"  "+System.currentTimeMillis());
        }
    },1000,2000);
}
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ym8DcEmP-1603163485909)(https://i.imgur.com/f5551y1.png)]

Timer 是调度器,TimerTask 调度任务。


##Timer类

###构造方法

Timer() 创建一个新的计时器。

Timer(boolean isDaemon) 创建一个新的定时器,其相关线程可以指定为 run as a daemon 。

Timer(String name) 创建一个新的定时器,其相关线程具有指定的名称。

Timer(String name, boolean isDaemon) 创建一个新的定时器,其相关线程具有指定的名称,可以指定为 run as a daemon 。

若指定为守护进程有什么效果呢?

//将上面程序的new Timer时候传入参数true
Timer timer=new Timer(true);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Bduindu-1603163485911)(https://i.imgur.com/fVjH9hl.png)]

因为是守护进程,main线程结束,所以不会执行直接结束。

###方法

void cancel() 终止此计时器,丢弃任何当前计划的任务,将任务队列中的全部任务清空。

import java.util.Timer;
import java.util.TimerTask;

public class TestTimer {
public static void main(String[] args) {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            System.out.println("我执行了" + "  " + System.currentTimeMillis());
        }
    }, 1000, 2000);

    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    timer.cancel();

}
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-POqEAnyC-1603163485913)(https://i.imgur.com/zFGIz4S.png)]


TimerTask类中的cancel()方法将自身从任务队列中清除。

	final Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    TimerTask timerTaskTwo = new TimerTask() {
        @Override
        public void run() {
            System.out.println("Two运行了,现在" + new Date());
        }
    };
    timer.schedule(timerTask,1000,2000);
    timer.schedule(timerTaskTwo,1000,2000);
    try {
        Thread.sleep(6000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    timerTaskTwo.cancel();

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NKFbh4ZE-1603163485914)(https://i.imgur.com/bhVJWyD.png)]


void schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TestTimer {
public static void main(String[] args) throws ParseException {
    final Timer timer = new Timer();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //时间已经过去
    String dateString = "2018-06-19 21:19:00";
    String dateStringTwo = "2018-06-19 21:28:00";
    Date date = dateFormat.parse(dateString);
    Date dateTwo = dateFormat.parse(dateStringTwo);
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    TimerTask timerTaskTwo = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    timer.schedule(timerTask, date);
    timer.schedule(timerTaskTwo, dateTwo);

}
}

结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBqCpxMZ-1603163485915)(https://i.imgur.com/45AwwmH.png)]

已经过去的时间一运行就执行任务,而未到达的时间等到达后执行任务。

为什么程序执行完还不结束?

这是因为系统默认当 Timer 运行结束后,如果没有手动终止,那么则只有当系统的垃圾收集被调用的时候才会对其进行回收终止。或调用 Timer 类自带的 cancel() 方法,实现 Timer 的终止。


void schedule(TimerTask task, Date firstTime, long period)从指定的时间开始 ,对指定的任务执行重复的 固定延迟执行 。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TestTimer {
public static void main(String[] args) throws ParseException {
    final Timer timer = new Timer();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //时间已经过去
    String dateString = "2018-06-19 21:44:40";
    Date date = dateFormat.parse(dateString);
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    timer.schedule(timerTask, date,2000);

}
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HxiRSy7C-1603163485916)(https://i.imgur.com/dkgaGhK.png)]


void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。

	final Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    timer.schedule(timerTask, 2000);

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xUfxUwXe-1603163485917)(https://i.imgur.com/R4pUZ9z.png)]


void schedule(TimerTask task, long delay, long period) 在指定的延迟之后开始 ,重新执行固定延迟执行的指定任务。

	final Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    timer.schedule(timerTask, 3000,2000);

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ax2hsuTe-1603163485918)(https://i.imgur.com/7K7V4a6.png)]


void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 从指定的时间开始 ,对指定的任务执行重复的固定速率执行 。

    final Timer timer = new Timer();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //时间已经过去
    String dateString = "2018-06-19 21:44:40";
    Date date = dateFormat.parse(dateString);
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    //timer.schedule(timerTask, date,2000);
    timer.scheduleAtFixedRate(timerTask, date,2000);

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JM5fW488-1603163485918)(https://i.imgur.com/4xecFcR.png)]

之前未执行的任务都被执行了。

改为

timer.schedule(timerTask, date,2000);
//timer.scheduleAtFixedRate(timerTask, date,2000);

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FOjdBDf4-1603163485919)(https://i.imgur.com/Vsy5G1Z.png)]
之前未执行的任务都被没有被执行。


void scheduleAtFixedRate(TimerTask task, long delay, long period) 在指定的延迟之后开始 ,重新执行固定速率的指定任务。

	final Timer timer = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println("运行了,现在" + new Date());
        }
    };
    timer.scheduleAtFixedRate(timerTask, 3000,2000);

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBC7FoLQ-1603163485920)(https://i.imgur.com/3kiRXfl.png)]


##源码探索

若创建了无参对象 new Timer() ,查看 Timer 无参构造器:

public Timer() {
    this("Timer-" + serialNumber());
}

这边调用 Timer(String name) 构造器,查看 serialNumber() 方法

/**
 * This ID is used to generate thread names.
 */
//初始化从 0 开始
private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
private static int serialNumber() {
	//返回值后加 1
    return nextSerialNumber.getAndIncrement();
}

验证:

 	Timer timer = new Timer();
    Timer timerTwo = new Timer();
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            System.out.println("运行了,现在" + new Date());
        }
    };
    TimerTask timerTaskTwo = new TimerTask() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
            System.out.println("Two运行了,现在" + new Date());
        }
    };
    timer.schedule(timerTask, 1000);
    timerTwo.schedule(timerTaskTwo, 1000);

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9Wp6fHk-1603163485920)(https://i.imgur.com/ZxKsUBX.png)]

查看 Timer(String name)

public Timer(String name) {
    thread.setName(name);
    thread.start();
}

查看 thread

private final TaskQueue queue = new TaskQueue();

private final TimerThread thread = new TimerThread(queue);

TaskQueue 任务队列用来存放任务即TimerTask,thread来跑任务。


###查看 TimerThread 类

boolean newTasksMayBeScheduled = true;

newTasksMayBeScheduled 用于控制当queue任务队列为空时,定时线程是否应该立刻终止(false立刻终止)

private TaskQueue queue;

queue 任务队列。

public void run() {
    try {
		//最核心
        mainLoop();
    } finally {
        // Someone killed this Thread, behave as if Timer cancelled
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references
        }
    }
}

最核心

private void mainLoop() {
    while (true) {
        try {
            TimerTask task;
            boolean taskFired;
			// 获取任务队列的锁
            synchronized(queue) {
				// 如果任务队列为空,并且线程没有被cancel()
            	// 则线程等待queue锁,queue.wait()方法会释放获得的queue锁
            	// 这样在Timer中sched()方法才能够获取到queue锁
                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 { 
							// 任务是重复执行任务,计算任务下一次应该被执行的时间,并重新排序任务队列
                            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) {
        }
    }
}

到这里你会发现为什么正常情况下Timer运行完任务后一直不结束因为一直是while(true) 除非遇到不能捕获的异常或break才会跳出。

if (queue.isEmpty())
    break;

如果是设置了newTasksMayBeScheduled状态为false跳出,也就是调用了cancel,那么queue就是空的,此时就直接跳出外部的死循环,所以cancel就是这样实现的。


Timer类中sched方法

下面所有的 schedule 方法都调用了sched方法

void schedule(TimerTask task, long delay)

void schedule(TimerTask task, Date time)

void schedule(TimerTask task, long delay, long period)

void schedule(TimerTask task, Date firstTime, long period)

void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

void scheduleAtFixedRate(TimerTask task, long delay, long period)

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) {
		//判断Timer是否已经取消
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");

        synchronized(task.lock) {
			//TimerTask.VIRGIN标记任务没有被调度
            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();
    }
}

为什么要做notify操作呢?

因为mainLoop()里面的wait()。


Timer类中cancel方法

public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();  // In case queue was already empty.
    }
}

修改newTasksMayBeScheduled为false,删除queue队列中的任务


###查看TaskQueue类

private TimerTask[] queue = new TimerTask[128];

TimerTask数组,初始数组大小为128。

int size() {
    return size;
}

size()任务队列的长度

 void add(TimerTask task) {
    // Grow backing store if necessary
    if (size + 1 == queue.length)
		//扩2倍长度
        queue = Arrays.copyOf(queue, 2*queue.length);

    queue[++size] = task;
    fixUp(size);
}

add(TimerTaskt)为增加一个任务

TimerTask getMin() {
    return queue[1];
}

getMin()获取当前排序后最近需要执行的一个任务,下标为1,队列头部0是不做任何操作的

 TimerTask get(int i) {
    return queue[i];
}

get(int i)获取指定下标的数据,当然包括下标 0

void removeMin() {
    queue[1] = queue[size];
    queue[size--] = null;  // Drop extra reference to prevent memory leak
    fixDown(1);
}

removeMin()为删除当前最近执行的任务,也就是第一个元素,通常只调度一次的任务,在执行完后,调用此方法,就可以将TimerTask从队列中移除。

void quickRemove(int i) {
    assert i <= size;

    queue[i] = queue[size];
    queue[size--] = null;  // Drop extra ref to prevent memory leak
}

quickRmove(int i) 删除指定的元素,一般来说是不会调用这个方法的,这个方法只有在 Timer 发生 purge 的时候,并且当对应的 TimerTask调用了 cancel 方法的时候,才会被调用这个方法,也就是取消某个TimerTask,然后就会从队列中移除(注意如果任务在执行中是,还是仍然在执行中的,虽然在队列中被移除了),还有就是这个 cancel 方法并不是 Timer 的 cancel 方法而是 TimerTask,一个是调度器的,一个是单个任务的,最后注意,这个 quickRmove 完成后,是将队列最后一个元素补充到这个位置,所以此时会造成顺序不一致的问题,后面会有方法进行回补。

void rescheduleMin(long newTime) {
    queue[1].nextExecutionTime = newTime;
    fixDown(1);
}

rescheduleMin(long newTime)是重新设置当前执行的任务的下一次执行时间,并在队列中将其从新排序到合适的位置,而调用的是后面说的fixDown方法。

boolean isEmpty() {
    return size==0;
}

isEmpty() 判空。

void clear() {
    // Null out task references to prevent memory leak
    for (int i=1; i<=size; i++)
        queue[i] = null;

    size = 0;
}

clear() 清理。

对于 fixUp(int k) 和 fixDown(int k) 方法来讲,前者是当新增一个task的时候,首先将元素放在队列的尾部,然后向前找是否有比自己还要晚执行的任务,如果有,就将两个任务的顺序进行交换一下。而fixDown正好相反,执行完第一个任务后,需要加上一个时间片得到下一次执行时间,从而需要将其顺序与后面的任务进行对比下。

heapify(),其实就是将队列的后半截,全部做一次fixeDown的操作,这个操作主要是为了回补quickRemove方法,当大量的quickRmove后,顺序被打乱后,此时将一半的区域做一次非常简单的排序即可。


###TimerTask抽象类

//对象锁
final Object lock = new Object();

//task状态初始VIRGIN
int state = VIRGIN;

//这项任务尚未安排好
static final int VIRGIN = 0;

//这个任务计划执行。如果它是一个非重复的任务,那么它还没有被执行
static final int SCHEDULED   = 1;

//此非重复任务已经执行(或目前正在执行)并没有被取消。
static final int EXECUTED    = 2;

//此任务被取消了
static final int CANCELLED   = 3;

//任务下次执行时间
long nextExecutionTime;

//重复任务的周期(毫秒)。一个积极的值表示固定利率执行。负值表示固定延迟执行。值为0表示非重复任务。
long period = 0;

//取消任务
public boolean cancel()

//计划执行时间
public long scheduledExecutionTime() {
    synchronized(lock) {
        return (period < 0 ? nextExecutionTime + period
                           : nextExecutionTime - period);
    }
}

#总结

参考
Java多线程编程核心技术
https://blog.csdn.net/xieyuooo/article/details/8607220
https://www.jianshu.com/p/58a5b0853451

Timer类其实内部有个thread,还有一个queue(来存储task),task会按一定的方式将任务排队处理,thread中的run方法无限循环task队列,执行task中的run()。

才学疏浅,有什么问题请大家指出来。十分感谢!

最后

以上就是爱笑夕阳为你收集整理的定时器Timer的全部内容,希望文章能够帮你解决定时器Timer所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部