定时器详解

定时器:Timer类

常用方法方法:

1.schedule(TimeTask timetask,long delay,(long period)):

TimeTask:实现了Runnable类,实现时需要重写run方法

delay:表示延迟多少(decay)后开始执行任务,单位是毫秒,这个参数也可以是日期(Date)

period:周期时间,表示定时器循环执行任务之间的间隔时间,时间是毫秒

如果没有period参数,那么就只执行一次任务内容

2.scheduleAtFixedRate(TimeTask timetask,long delay,long period):

参数和schedule的参数相同,其作用为定时器设置循环执行的内容,第一次执行内容的延迟时间,循环的周期时间。可以看出schedule方法的功能其实已经包括这个方法了

3.cancel():关闭计时器


schedule(TimeTask timetask,long delay)

public static void main(String[] args) {
        System.out.println("任务三秒后开启");
        
        Timer t = new Timer();
        //定时执行任务 表示几秒后执行run方法里面的内容
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务开启");
            }
        },3000);
    }

schedule(TimeTask timetask,long delay,long period):

 public static void main(String[] args) {
        System.out.println("任务三秒后开启");

        Timer t = new Timer();
        //定时执行任务 表示几秒后执行run方法里面的内容
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("开始以1秒的时间间隔循环执行任务");
            }
        },2000,1000);
    }

scheduleAtFixedRate(TimeTask timetask,long delay,long period):

 public static void main(String[] args) {
        System.out.println("任务两秒后开启");

        Timer t = new Timer();
        //定时执行任务 表示几秒后执行run方法里面的内容
        t.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务开启");
            }
        },2000);

        t.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println("开始循环任务(间隔1s)");
            }
        },2000,1000);
    }

指定定时器执行固定个任务后结束
public static void main(String[] args) {
        Timer t = new Timer();
        int timeCount = 5;
        long delay = 1000;
        long period = 1000;

        t.schedule(new TimerTask() {
            int count = 1;
            @Override
            public void run() {
                if(count >= timeCount){
                    t.cancel();
                }
                System.out.println("执行任务:"+count);
                count++;
            }
        },delay,period);
    }

模拟实现定时器

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

class Task implements Comparable<Task>{
    //这个类用来描述任务的
    private Runnable runnable;

    //执行的任务时间
    private long time; //time + System.currentTimeMillis()

    public Task(Runnable runnable,long time){
        this.runnable = runnable;
        this.time = time;
    }

    public long getTime(){
        return time ;
    }

    public void run() {
        runnable.run();
    }

    //比较规则 执行时间在前的先执行
    @Override
    public int compareTo(Task o) {
        return (int)(this.time - o.time);
    }
}
public class MyTimer{
    //一个阻塞队列
    private BlockingQueue<Task> queue = new PriorityBlockingQueue<>();

    //扫描线程
    private Thread t = null;

    public MyTimer(){
            t = new Thread(() -> {
                while(true) {
                    try {
                        Task task = queue.take();
                        if (task.getTime() > System.currentTimeMillis()) {
                            //还没到时间
                            queue.put(task);
                        } else {
                            //执行任务
                            task.run();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }

    public void schedule(Runnable runnable,long time) throws InterruptedException {
        //在这一步加System.currentTimeMillis() 不能在getTime方法中加,不然就会一直大于System.currentTimeMillis()
        Task task = new Task(runnable, time+System.currentTimeMillis());
        queue.put(task);
    }

    public static void main(String[] args) throws InterruptedException {
        MyTimer m = new MyTimer();
        m.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是任务1");
            }
        },2000);

        m.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是任务2");
            }
        },1000);
    }
}

问题:
1.忙等

假设此时时间是上午9点,第一个任务的时间是上午10点,那么在这一个小时内,程序会一直重复执行一个代码:

 Task task = queue.take();
 if (task.getTime() > System.currentTimeMillis()) {
     //还没到时间
     queue.put(task);
 }

这行代码可能会循环个非常多亿次,且别忘了,优先级队列的底层是用堆实现的,每当我们取出一个元素又出现插入时,根据堆的调整,此元素又会在堆顶,而每一层调整都是有开销的,故这一时间段的开销是重复且多余的,所以我们就等一次,等堆顶任务执行时间与当前时间的差值就可,修改代码:

 Task task = queue.take();
 if (task.getTime() > System.currentTimeMillis()){
     //还没到时间
     queue.put(task);
     Thread.sleep(task.getTime() - System.currentTimeMillis());

大伙们是不是觉得此时的代码就已经完美了?!!

然而并不是! 我们并不能使用sleep休眠来休眠两时间差

试想一下,如果咱们休眠的时候突然插进来了一个上午9.30执行的任务,那么这个任务就不会被执行到!故使用带参版本的wait()方法才是最好的选择

再次修改代码:

 if (task.getTime() > System.currentTimeMillis()){
     //还没到时间
     queue.put(task);
     synchronized (this){
        this.wait(task.getTime() - System.currentTimeMillis());
     }


 public void schedule(Runnable runnable,long time) throws InterruptedException {
        Task task = new Task(runnable, time+System.currentTimeMillis());
        queue.put(task);
        //唤醒t线程里的wait操作
        this.notify();
     }

这个时候代码看起来是不是似乎万无一失了已经!

but,我们来考虑一个极端极端极端极端的情况


2.极端情况
 if (task.getTime() > System.currentTimeMillis()){
     //还没到时间
     queue.put(task);
     synchronized (this){
        this.wait(task.getTime() - System.currentTimeMillis());
     }

如果线程1在执行到queue.put(task)时,恰好被调度走了,此时另一个线程调用schedule方法,且如果当线程2的任务时间小于线程1的任务时间时,此时线程2的任务就不会执行到。因为线程2的notify没作用,线程1都还没有执行到wait方法,但线程1重新执行时,此时的时间差已经是固定了的,但是这个时间差要大于线程2任务执行的时间,故线程2就会被"极端"地错过


完美代码

加大锁的力度:造成此极端情况的原因即为:take和wait是多步操作,非原子性

public MyTimer(){
            t = new Thread(() -> {
                while(true) {
                    synchronized (this){
                        try {
                            Task task = queue.take();
                            if (task.getTime() > System.currentTimeMillis()) {
                                //还没到时间
                                queue.put(task);
                                this.wait(task.getTime() - System.currentTimeMillis());
                            } else {
                                //执行任务
                                task.run();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            t.start();
        }
相关推荐
一个闪现必杀技1 分钟前
Python入门--函数
开发语言·python·青少年编程·pycharm
Fan_web4 分钟前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
龙图:会赢的8 分钟前
[C语言]--编译和链接
c语言·开发语言
IT学长编程1 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码1 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
XKSYA(小巢校长)2 小时前
NatGo我的世界联机篇
开发语言·php
Cons.W2 小时前
Codeforces Round 975 (Div. 1) C. Tree Pruning
c语言·开发语言·剪枝
憧憬成为原神糕手2 小时前
c++_ 多态
开发语言·c++
VBA63372 小时前
VBA信息获取与处理第三个专题第三节:工作薄在空闲后自动关闭
开发语言