java多线程之定时器

一、 简介

1. 概念

定时功能在java中主要是通过 Timer类实现,因为它在内部还是使用多线程的方式进行处理,所以和多线程技术还是有非常大的管理

2. 定时器的使用

在JDK库中Timer类主要负责计划任务的功能,也就是在指定时间开始执行某一个任务,Timer类的主要功能就是设置计划任务,封装任务的类确是TimerTask,因为TimerTask是一个抽象类,所以执行计划任务的代码要放入TimerTask子类中。

二、 常用方法介绍

1. Schedule(TimTask task,Data time)

该方法的作用是在指定的日期执行一次某一任务

  1. 执行任务的时间晚于当前时间(在未来执行)的效果

如果Data time,该参数的时间晚于现在的时间就是未来执行,而该参数的时间早于现在的执行就是立即执行的意思

测试代码如下

public class Main {
    public static void main(String[] args) throws InterruptedException {
        long nowTime=System.currentTimeMillis();
        System.out.println("当前时间为:"+nowTime);

        //计划时间比当前晚10s
        long scheduleTime=(nowTime+10000);
        Mytest task=new Mytest();
        Timer timer=new Timer();
        Thread.sleep(1000);
        timer.schedule(task,new Date(scheduleTime));

        Thread.sleep(Integer.MAX_VALUE);

    }
}
class Mytest extends TimerTask{
    @Override
    public void run() {
        System.out.println("任务执行了,时间为:"+System.currentTimeMillis());

    }

}

测试后你会发现过了一段时间才执行TimerTask中的任务

  1. 线程TimerThread不销毁的原因

其实上面10s后任务虽然执行完了,但是进程并没有销毁,因为创建Timer类的同时会创建一个新进程(TimerThread—实现计时器的任务执行线程,该线程等待计时器队列上的任务,在触发时执行它们,重新计划重复任务,并从队列中删除已取消的任务和已花费的非重复任务),Timer源码如下:

在这里插入图片描述
(图中应该是线程,有笔误)

而这线程是进程不结束的原因,因为它的run方法调用了一个mainLoop方法,该方法内部有个死循环,只有到达特定要求才会退出死循环,我们看它源码:

 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) {
            }
        }
    }
}
  • 使用while循环对queue.isempty()&&newTasksMayBescheduled条件进行判断
  • 当满足时,执行wait方法暂停该线程,等待被唤醒
  • 唤醒的时机是执行了public void schedule(TimeTask task ,Data time)方法,说明要执行新的任务了
  • 唤醒后while继续判断前面的条件,如果有新的任务被安排,则会向下继续执行
  • if(queue.isempty())判断结果为true的时候,说明队列为空,那么执行break退出死循环
  • 如果不执行public void cancel()方法会使布尔变量newTasksMayBeSchedule的值由true变为false,进程不销毁就是这个原因,cancel源码如
    public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify();  // In case queue was already empty.
        }
    }

cancel方法的作用就是终止计时器,丢弃当前所有已安排的任务。这不会干扰正在执行的任务,一旦终止计时器,那么它的执行线程也会终止,并且无法根据它安排更多的任务。注意,在此计时器调用的计时器任务的run()方法内调用此方法,就可以确保正在执行的任务就是所执行的最后一个任务,虽然可以重复调用cancel方法,但后面都是无效的。

  1. 在定时器中执行多个TimerTask任务

创建的方法如下

//创建一个Timer
TImer timer= new Timer();
//创建多个任务
MyTask task1=new MyTask();
MyTask task2=new MyTask();
//执行schedule方法
timer.schedule(task1,new Date(scheduleTime1));
timer.schedule(task2,new Date(scheduleTime2));

由于多个任务的存在,因为TimerTask是以队列的方式一个一个被顺序执行的,所以执行时间有可能和预期的时间不一致,这是因为前面的任务可能执行时间过长,导致后面的任务被延时

2. Schedule(TimTask task,Data firstTime, long period)

改方法的作用是在指定的日期之后按指定的间隔周期无限地执行某一个任务,参数firstTime是循环任务开始的时间,period是任务循环的,和上面介绍的方法一样,该方法也有上面方法的特性:

  • 执行任务的时间晚于当前时间:任务会从指定时间开始,在指定的时间周期内开始循环执行任务
  • 执行任务的时间早于当前时间:任务同样会立即开始执行,且在指定的时间间隔内循环执行
  • 延时问题:同样该方法也会存在任务延时执行的问题
  • cancel方法:前面介绍了Timer类的cancel方法的特性它会终止所有没有执行的任务(清除队列中所有任务),而TimerTask类中的方法的作用是将自身从任务队列中消除,源码如下,可见该方法时将当前TimerTask任务的状态改为CANCELLED
   public boolean cancel() {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            return result;
        }
    }
  • 同一时间间隔内执行多个循环任务:算法是,当队列中有3个任务ABC时,这3个任务的执行顺序的算法是每次将最后一个任务放入队列头,在再执行队列头中任务的run方法。例如,ABC,CAB,BCA以此类推

3. Schedule(TimTask task, long delay)

该方法的作用是以执行schedule(Timer task, long delay)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务

4. Schedule(TimTask task, long delay, long period)

该方法的作用是以当前时间为参考时间,在此基础上延迟指定的毫秒数,再以某一时间为间隔无限次数地执行某一任务。

5. scheduleAtFixedRate(TimerTask task, Date firstTime,long period)

schedule方法和scheduleAtFixedRate方法的主要区别在于有没有追赶特性

  1. 测试schedule方法任务不延时(Date类型-其实就是Schedule(TimTask task,Data firstTime, long period)

代码测试如下:

public class Main {
    public static void main(String[] args) throws InterruptedException {
         Mytest task=new Mytest();
         long nowTime=System.currentTimeMillis();
         Timer timer=new Timer();
         timer.schedule(task,new Date(nowTime),3000);

    }
}
class Mytest extends TimerTask{
    @Override
    public void run() {
       try{
           System.out.println("begin timer="+System.currentTimeMillis());
           Thread.sleep(1000);
           System.out.println("end timer"+System.currentTimeMillis());
       }catch (InterruptedException e) {
           e.printStackTrace();
       }

    }

}

在这里插入图片描述
由结果可以知道,在不延时的情况下,如果执行任务的时间没有被延时,则下一次执行任务的开始时间就是上一次任务的开始时间加上Period时间,所谓的“不延时”是指执行任务的时间小于或等于period时间间隔

  1. 测试schedule方法任务不延时(long类型-其实就是Schedule(TimTask task, long delay, long period)

代码测试如下:

public class Main {
    public static void main(String[] args) throws InterruptedException {
         Mytest task=new Mytest();
         long nowTime=System.currentTimeMillis();
         Timer timer=new Timer();
         timer.schedule(task,new 3000,3000);

    }
}
class Mytest extends TimerTask{
    @Override
    public void run() {
       try{
           System.out.println("begin timer="+System.currentTimeMillis());
           Thread.sleep(1000);
           System.out.println("end timer"+System.currentTimeMillis());
       }catch (InterruptedException e) {
           e.printStackTrace();
       }

    }

}

在这里插入图片描述
在不延时的情况下,如果执行任务的时间没有被延时,则第一次执行任务的时间是任务开始的时间加上被延迟的时间,接下来执行任务的时间是上一次的开始时间加上period时间

  1. 测试shceudle方法的任务延迟(Date类型)
public class Main {
    public static void main(String[] args) throws InterruptedException {
         Mytest task=new Mytest();
         long nowTime=System.currentTimeMillis();
         Timer timer=new Timer();
         timer.schedule(task,new Date(nowTime),2000);

    }
}
class Mytest extends TimerTask{
    @Override
    public void run() {
       try{
           System.out.println("begin timer="+System.currentTimeMillis());
           Thread.sleep(5000);
           System.out.println("end timer"+System.currentTimeMillis());
       }catch (InterruptedException e) {
           e.printStackTrace();
       }

    }

}

在这里插入图片描述
从控制台打印的结果可以看出,在延时的情况下,如果执行任务的时间被延时,那么下一次任务的执行时间参考的时上一次任务“结束”的时间,同样子long类型也是这样,同样scheduleAtFixedRate在long和date类型下在延迟和不延迟的情况下都是一样的

  1. 验证schedule方法不具有追赶执行性
    执行下面代码
public class Main {
    public static void main(String[] args) throws InterruptedException {
         Mytest task=new Mytest();
         long nowTime=System.currentTimeMillis();
        System.out.println("现在执行时间:"+nowTime);
        long runtime=nowTime-20000;
        System.out.println("计划执行时间:"+runtime);
        Timer timer=new Timer();
        timer.scheduleAtFixedRate(task,new Date(runtime),2000);

    }
}
class Mytest extends TimerTask{
    @Override
    public void run() {
           System.out.println("begin timer="+System.currentTimeMillis());
           System.out.println("end timer"+System.currentTimeMillis());
    }

}

在这里插入图片描述

由结果可以看出,计划在1684417340323和1684417320323之间的任务没有追赶执行,所谓的追赶执行,就是原来计划在前面那段时间的任务,由于时间已经过了,只能从现在开始给它补上来,这就是追赶。而该方法也有追赶执行性

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值