Timer 定时器

为什么要使用定时器呢?

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

概念:Java java.util.Timer

Java java.util.Timer是一种定时器工具,用来在一个后台线程计划执行指定任务。它可以计划执行一个任务一次或反复多次.

在这里插入图片描述

概念:Java TimerTask

1.TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。具体的任务在TimerTask中run接口中实现。通过Timer中的schedule方法启动定时任务
Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("11232");
        }
}, 200000 , 1000);

在这里插入图片描述

Timer 类讲解

在这里插入图片描述

Timer.java中含有3个类:Timer、TimerThread、TaskQueue。

TaskQueue

  • TaskQueue中存放一些列将要执行的TimerTask,以数组的形式存放,下标约小(注:下标为0不处理,即使用的最小下标为1),则表明优先级越高。
    在这里插入图片描述

TimerThread

  • TimerThread继承Thread类,会一直从TaskQueue中获取下标为1的TimerTask进行执行。并根据该TimerTask是否需要重复执行来决定是否放回到TaskQueue中。
    在这里插入图片描述

Timer

  • Timer用于配置用户期望的任务执行时间、执行次数、执行内容。它内部会配置,调度TimerThread、TaskQueue。

Timer 构造方法:

  1. Timer timer = new Timer(); //其中会调用this(“Timer-” + serialNumber());, 即它以Timer+序列号为该定时器的名字
  2. Timer timer = new Timer(String name); //以name作为该定时器的名字
  3. Timer timer = new Timer(boolean isDeamon); //是否将此定时器作为守护线程执行
  4. Timer timer = new Timer(name, isDeamon); //定时器名字, 是否为守护线程
创建一个 Timer 对象就是新启动了一个线程,但是这个新启动的线程,并不是守护线程,它一直在后台运行.

运行定时器

  1. 启动一个定时器实质是启动一个线程
  2. 所有的task都是TimerTask的子类
  3. 所有time都是Date类型的日期
  4. 所有delay和period都是long类型的延迟时间, 单位为毫秒

API:

schedule

  • timer.schedule(task, time);
    在time时间执行task任务1次

  • timer.schedule(task, delay);
    在延迟delay毫秒后执行task任务1次

  • timer.schedule(task, firstTime, period);
    在firsttime时间执行task1次,之后定期period毫秒时间执行task, 时间如果为过去时间,不会执行过去没有执行的任务, 但是会马上执行

  • timer.schedule(task, delay, period);
    在延迟delay后执行task1次,之后定期period毫秒时间执行task, 时间如果为过去时间, 不会执行过去没有执行的任务, 但是会马上执行

scheduleAtFixedRate

  • timer.scheduleAtFixedRate(task, firstTime, period);
    在firstTime时间执行task一次, 以后每隔period毫秒执行1次, 时间如果为过去时间, 会执行过去没有执行的任务, 但是会马上执行
  • timer.scheduleAtFixedRate(task, delay, period);
    在delay毫秒后执行task一次, 以后每隔period毫秒执行1次, 时间如果为过去时间, 会执行过去没有执行的任务, 但是会马上执行

启动任务schedule 与 scheduleAtFixedRate的区别

  • 方法schedule 和方法 scheduleAtFixedRate 在使用上基本没什么差别,就是 scheduleAtFixedRate 具有追赶执行性
  • 什么意思呢?就是如果任务 在周期性运行过程中被打断了,scheduleAtFixedRate会尝试把之前落下的任务补上运行。而schedule就不管了,接着运行接下来的任务就行了.

1、在指定日期运行定时器任务,只运行一次

如果date日期在今天之前,则启动定时器后,立即运行一次定时任务run方法
如果date日期在今天之后,则启动定时器后,会在指定的将来日期运行一次任务run方法
    public static void main(String[] args) throws  ParseException {
        String sdate = "2018-02-14";
        SimpleDateFormat sf = new SimpleDateFormat("yy-MM-dd");
        Date date = sf.parse(sdate);

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                System.out.println("系统正在运行……");
            }
        }, date); //在指定的日期运行一次定时任务
        /*如果date日期在今天之前,则启动定时器后,立即运行一次定时任务run方法*/
        /*如果date日期在今天之后,则启动定时器后,会在指定的将来日期运行一次任务run方法*/
    }

在这里插入图片描述

2、在距当前时刻的一段时间后运行定时器任务,只运行一次

指定启动定时器5s之后运行定时器任务run方法,并且只运行一次
  public static void main(String[] args) throws  ParseException {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                System.out.println("系统正在运行……");
            }
        }, 5000); //指定启动定时器5s之后运行定时器任务run方法,并且只运行一次

    }

在这里插入图片描述

3、在指定的时间后,每隔指定的时间,重复运行定时器任务

如果指定的date时间是当天或者今天之前,启动定时器后会立即每隔2s运行一次定时器任务
如果指定的date时间是未来的某天,启动定时器后会在未来的那天开始,每隔2s执行一次定时器任务
public static void main(String[] args) throws  ParseException {
        String sdate = "2018-02-10";
        SimpleDateFormat sf = new SimpleDateFormat("yy-MM-dd");
        Date date = sf.parse(sdate);

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                System.out.println("系统正在运行……");
            }
        }, date, 2000);
        /*如果指定的date时间是当天或者今天之前,启动定时器后会立即每隔2s运行一次定时器任务*/
        /*如果指定的date时间是未来的某天,启动定时器后会在未来的那天开始,每隔2s执行一次定时器任务*/

    }

在这里插入图片描述

4、在距当前时刻的一段指定距离后,每隔指定时间运行一次定时器任务

当启动定时器后,5s之后开始每隔2s执行一次定时器任务
 public static void main(String[] args) throws  ParseException {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                System.out.println("系统正在运行……");
            }
        }, 5000, 2000);
        /*当启动定时器后,5s之后开始每隔2s执行一次定时器任务*/
    }

在这里插入图片描述

停止定时器

停止定时器实质是终止Timer的线程。默认情况下,创建的Timer线程会一直执行,如果要停止的话主要有以下四种方法终止Timer线程:
  1. 调用Timer的cancel方法;
  2. 把Timer线程设置成Daemon守护线程,当所有的用户线程结束后,那么守护线程也会被终止;
  3. 当所有的任务执行结束后,删除对应Timer对象的引用,线程也会被终止;
  4. 调用System.exit方法终止程序

举例用cancel方法终止Timer线程

    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                System.out.println("系统正在运行……");
//              timer.cancel(); //可以在任何时刻调用cancel方法终止timer线程
            }
        }, 5000, 2000);

        /*如果主线程不休眠一段时间,就执行了cancel方法,那么定时器还没来得及执行就会被关闭*/
        Thread.sleep(6000);
        timer.cancel();
    }

举例让timer线程成为一个daemon守护线程

  • 可以在创建timer时使用new Timer(true)达到这个目地,这样当程序只有daemon线程的时候,它就会自动终止运行。
  public static void main(String[] args) throws  ParseException {
        Timer timer = new Timer(true);
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                System.out.println("系统正在运行……");
            }
        }, 5000, 2000);
        /*当启动定时器后,5s之后开始每隔2s执行一次定时器任务*/
    }

一些注意的问题

  • 每一个Timer仅对应唯一一个线程。
  • Timer不保证任务执行的十分精确。
  • Timer类的线程安全的。

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) {
		//判断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)//将TimerTask放到队列中,并进行队列排序
            queue.notify();//如果队列里恰好下标为1的任务为当前的task,则直接唤醒
                            //注意最小的值是从下标为1的获取,queue[0]其实没用到

    }
}

  • 数据已经放到queue中了,那么看下是什么时候执行的。在之前Timer的构造函数这块,有一句是:thread.start();说明TimerThread在Timer初始化之后就一直启用着,那看下它的处理。

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
            }
        }
    }
 
    /**
     * 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
                   //如果队列为空并且是有标志位,则等待。没有标志位的情况为不在需要执行timer了,比如cancel或被gc的时候
                    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();//获取就近的task
                    synchronized(task.lock) {
                       //如果该task已经被置为cancelled,则将它从队列里面移出
                        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) { // period表示该task是一次性的,用完就移出
                                queue.removeMin();//移出task,这块会有queue的重新排序
                                task.state = TimerTask.EXECUTED;//更新状态为执行中
                            } else {
                        //可重复执行的task操作,将重新计算下次执行时间,并重新排序    
                //重点,此处解释为什么period分正负:区别schedule方法和scheduleAtFixedRate
                        //如果是负数,则以当前时间为准,往后计算下次执行时间
                        //如果是正数,则以理论时间为准,往后计算下次执行时间
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // 如果还没到任务执行时间就处于等待
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // 到执行时间了
                //执行task中的run方法,而不是start方法,所以并不是另起一个线程进行操作
                    task.run();
            } catch(InterruptedException e) {//如果是不能捕获的异常,就会有风险了
            }
        }
    }
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐