[JavaEE]定时器


专栏简介: JavaEE从入门到进阶

题目来源: leetcode,牛客,剑指offer.

创作目标: 记录学习JavaEE学习历程

希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长.

学历代表过去,能力代表现在,学习能力代表未来! 


目录: 

1.定时器的概念

2.标准库中的定时器

3.实现定时器

3.1定时期的构成:

3.2 实现步骤:


1.定时器的概念

定时器类似于一个"闹钟" , 是软件开发中的一个重要组件 , 达到一个设定时间后就会执行某段代码.

例如网络通信中 , 如果对方500ms都没有返回数据 , 那么就执行定时器中的任务:断开重连.

例如实现一个Map , 希望 Map 中的 key 在3s后过期(自动删除).

...............................

类似于以上的场景就需要用到定时器.


2.标准库中的定时器

  • 标准库中提供了一个 Timer 类 , Timer 类的核心方法是 schedule().
  • schedule()方法包含两个参数 , 第一个参数指定即将要执行的任务代码 , 第二个参数指定多长时间后执行(单位ms)

public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },500);
    }

3.实现定时器

3.1定时期的构成:

  • 一个带优先级的阻塞队列 , 用来存放待执行的任务.

为什么要带优先级? 如果优先级队列按待执行时间(delay)来排序的话 , 每次只需取出队首的元素就可高效的把delay最小的任务给找出来.

  • 队列中每一个元素是 Task 对象.
  • Task 中带有一个时间属性 , 队首元素就是即将要执行的元素.
  • 同时有一个扫描线程一直扫描队首元素 , 查看队首元素是否到达执行时间.

3.2 实现步骤:

  • 1.Timer 类提供的核心方法是 schedule() , 用于注册一个任务并指定这个任务何时执行.
class MyTimer{
    public void schedule(Runnable runnable, long after){
        //具体执行的任务
    }
}
  • 2.Task 类用于描述一个任务 , 里面包含一个 Runnable 对象和一个 time毫秒时间戳. 该类的对象需要放入优先级队列中 , 因此需要实现 Comparable 接口.
class MyTask implements Comparable<MyTask>{
    private Runnable runnable;
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime(){
        return time;
    }
    //执行任务
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTask1 o) {
        return (int) (this.time-o.time);
    }
}
  • 3.Timer 实例中 , 通过PriorityQueue来组织若干个 Task 对象 , 通过 schedule 往队列中插入一个个 Task 对象.
class MyTimer{
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable , long after){
        MyTask1 myTask = new MyTask(runnable , System.currentTimeMillis()+after);
        queue.put(myTask);
    }
}
  • 4.Timer 类中存在一个扫描线程 , 不断的查看队首元素是否到达执行时间.
class MyTimer1 {
    Thread scan = null;
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
    }

    public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                try {
                    MyTask1 myTask1 = queue.take();
                    if (System.currentTimeMillis() < myTask1.getTime()) {
                        //时间还没到把任务塞回去
                        queue.put(myTask1);
                    } else {
                        //时间到了执行任务
                        myTask1.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        scan.start();
    }
}

但当前代码存在一个很严重的问题 , 就是while(true)循环速度太快 , 造成了无意义的 CPU 浪费.

  • 5. 借助 wait/notify 来解决 while(true) 问题 , 并且修改 MyTimer的 schedule方法 , 一但有新的任务加入就通知 wait 再次判断.
public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
        //一但加入新的元素就唤醒 wait ,重新判断
        synchronized (this) {
            this.notify();
        }
    }
public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                try {
                    MyTask1 myTask1 = queue.take();
                    if (System.currentTimeMillis() < myTask1.getTime()) {
                        //时间还没到把任务塞回去
                        queue.put(myTask1);
                        synchronized (this) {
                            this.wait(myTask1.getTime()-System.currentTimeMillis());
                        }
                    } else {
                        //时间到了执行任务
                        myTask1.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        scan.start();
    }

此时代码还存在一点瑕疵 , 假设当前时间是 13:00 如果队首的任务执行时间是 14:00  , 这时当代码执行到 queue.put(myTask1);时该线程被 CPU 调度走 , 与此同时另一个线程调用 schedule 方法 , 注册一个 13:30 执行的任务 , 将其放入队首并通知 wait.但此时 notify 方法空打一炮 , 等到扫描线程被调度回来时 , wait 还是要等待1h , 这样机会导致 13:30 的任务错过其执行时间.

产生上述问题的根本原因是 , take() 和 wait 操作不是原子的 , 如果在 take() 和 wait 之间加上锁 , 保证这个执行过程中不会有新的任务进来 , 问题自然解决.

public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                synchronized (this) {
                    try {
                        MyTask1 myTask1 = queue.take();
                        if (System.currentTimeMillis() < myTask1.getTime()) {
                            //时间还没到把任务塞回去
                            queue.put(myTask1);
                            this.wait(myTask1.getTime()-System.currentTimeMillis());
                        } else {
                            //时间到了执行任务
                            myTask1.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        scan.start();
    }

完整代码如下:

class MyTask1 implements Comparable<MyTask1>{
    private Runnable runnable;
    private long time;

    public MyTask1(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime(){
        return time;
    }
    //执行任务
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTask1 o) {
        return (int) (this.time-o.time);
    }
}
/**
 * 定时器类
 */
class MyTimer1 {
    Thread scan = null;
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
        //一但加入新的元素就唤醒 wait ,重新判断
        synchronized (this) {
            this.notify();
        }
    }

    public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                synchronized (this) {
                    try {
                        MyTask1 myTask1 = queue.take();
                        if (System.currentTimeMillis() < myTask1.getTime()) {
                            //时间还没到把任务塞回去
                            queue.put(myTask1);
                                this.wait(myTask1.getTime()-System.currentTimeMillis());
                        } else {
                            //时间到了执行任务
                            myTask1.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        scan.start();
    }
}
public class ThreadDemo8 {
    public static void main(String[] args) {
        MyTimer1 myTimer1 = new MyTimer1();
        myTimer1.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,1");
            }
        },300);
        myTimer1.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,2");
            }
        },600);
    }
}

猜你喜欢

转载自blog.csdn.net/liu_xuixui/article/details/128576133