定时器详解

定时器: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. <span style='color:red;'>定时器</span>

    定时器

    2024-04-20 10:06:10      12 阅读
  2. Unity3D 多线程定时器的原理与实现详解

    2024-04-20 10:06:10       19 阅读
  3. Unity3D 多线程定时器的原理与实现详解

    2024-04-20 10:06:10       22 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-20 10:06:10       19 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-20 10:06:10       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-20 10:06:10       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-20 10:06:10       20 阅读

热门阅读

  1. leetcode705-Design HashSet

    2024-04-20 10:06:10       60 阅读
  2. Unity发布webgl之后打开streamingAssets中的html文件

    2024-04-20 10:06:10       29 阅读
  3. vue3、vue2中nextTick源码解析

    2024-04-20 10:06:10       25 阅读
  4. 高级IO——React服务器简单实现

    2024-04-20 10:06:10       17 阅读
  5. 将图片数据转换为张量(Go并发处理)

    2024-04-20 10:06:10       20 阅读
  6. go第三方库go.uber.org介绍

    2024-04-20 10:06:10       19 阅读
  7. 前后端AES对称加密 前端TS 后端Go

    2024-04-20 10:06:10       27 阅读
  8. 文件上传下载

    2024-04-20 10:06:10       15 阅读
  9. obs二开_播放媒体源

    2024-04-20 10:06:10       15 阅读