定时器(Timer)

一、定时器是什么?

定时器类似于我们生活中的闹钟,可以设定一个时间来提醒我们。
而定时器是指定一个时间去执行一个任务,让程序去代替人工准时操作。

标准库中的定时器: 

方法作用
void schedule(TimerTask task, long delay)指定delay时间之后(单位毫秒)执行任务task
public static void main(String[] args) {
        Timer timer=new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("该起床了");
                System.out.println("三秒后刷牙洗脸");
            }
        },3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("起床后刷牙洗脸");
            }
        },6000);
    }

刚开始等待三秒

执行完第一个任务三秒后执行第二个任务

这段程序就是创建一个定时器,然后提交一个3000ms后执行的任务。

二、自定义定时器

我们自己实现一个定时器的前提是我们需要弄清楚定时器都有什么:
1.一个扫描线程,负责来判断任务是否到时间需要执行
2.需要有一个数据结构来保存我们定时器中提交的任务

创建一个扫描线程相对比较简单,我们需要确定一个数据结构来保存我们提交的任务,我们提交过来的任务,是由任务和时间组成的,我们需要构建一个Task对象,数据结构我们这里使用优先级队列,因为我们的任务是有时间顺序的,具有一个优先级,并且要保证在多线程下是安全的,所以我们这里使用:PriorityBlockingQueue比较合适。

首先我们构造一个Task对象

class MyTask implements Comparable<MyTask> {
    // 任务
    private Runnable runnable;
    // 任务执行的时间
    private long time;

    public MyTask(Runnable runnable, long delay) {
        // 校验任务不能为空
        if (runnable == null) {
            throw new IllegalArgumentException("任务不能为空.");
        }
        // 时间不能为负数
        if (delay < 0) {
            throw new IllegalArgumentException("执行时间不能小于0.");
        }
        this.runnable = runnable;
        // 计算出任务执行的具体时间
        this.time = delay + System.currentTimeMillis();
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
        if (this.getTime() > o.getTime()) {
            return 1;
        } else if (this.getTime() < o.getTime()) {
            return -1;
        } else {
            return 0;
        }

//        return (int) (this.time - o.getTime());
    }
}

这里有定时时间和系统当前的时间进行比较,所以使用后Comparable<MyTask>接口

MyTimer类:

  public MyTimer() {

        // 创建扫描线程
        Thread thread = new Thread(() -> {
            // 不断地扫描队列中的任务
            while (true) {
                try {

//                    System.out.println("实例化出来的对象 "+ this);
                    // 1. 从队列中取出任务
                    MyTask task = this.queue.take();
                    // 2. 判断到没到执行时间
                    long currentTime = System.currentTimeMillis();
                    if (currentTime >= task.getTime()) {
                        // 时间到了,执行任务
                        task.getRunnable().run();
                    } else {
                        // 当前时间与任务执行时间的差
                        long waitTime = task.getTime() - currentTime;
                        // 没有到时间,重新放回队列
                        queue.put(task);
                        synchronized (locker) {
                            // 加入等时间
                            locker.wait(waitTime);
                        }

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "scanThread");
        // 启动线程,真正去系统中申请资源
        thread.start();

        // 创建一个后台线程
        Thread daemonThread = new Thread(() -> {
            while (true) {
                synchronized (locker){
                    locker.notifyAll();
                }

                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        daemonThread.setDaemon(true);
        daemonThread.start();

    }

假设我们早上8点定时12点的任务,当阻塞队列添加任务时,如果再添加一个任务,这个任务的执行时间在阻塞对列添加之前任务时间之前,可能导致错过当前任务的执行时间,所以设置一个后台线程时刻唤醒Mytask线程避免出现线程安全问题!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值