自学内容网 自学内容网

[定时器]

目录

一. 定时器的使用

二. 定时器的实现


我们的日常生活中离不开闹钟, 代码中同样需要闹钟, 代码中的"闹钟"就叫做"定时器". 在java标准库中, 也提供了定时器的实现.

一. 定时器的使用

import java.util.Timer;
import java.util.TimerTask;

public class Demo31 {
    public static void main(String[] args) {
        Timer timer = new Timer(); //创建一个Timer类的对象
        timer.schedule(new TimerTask() {  
            // schedule(安排), 指定参数两个TimerTask和delay. TimerTask表示要执行的任务, delay表示在多长时间之后执行该任务
            @Override
            public void run() {
                System.out.println("hello world");
            }
        }, 3000);
        System.out.println("程序开始运行");
    }
}

java中 用Timer类中schedule来完成定时器的实现. schedule()需要指定两个参数:

(1) TimerTask, 这个参数表示了程序要执行的任务.

(2) delay (延时时间), 这个参数表示程序在多长时间之后执行该任务.

上面代码就实现了一个人非常简单的定时器, 这段代码表示: 先打印"程序开始运行", 等待3000ms之后再执行TimerTask中的任务 (打印"hello world") . 

Timer也可以安排多个任务:

import java.util.Timer;
import java.util.TimerTask;

public class Demo31 {
    public static void main(String[] args) {
        Timer timer = new Timer(); //创建一个Timer类的对象
        timer.schedule(new TimerTask() {
            // schedule(安排), 指定参数两个TimerTask和delay. TimerTask表示要执行的任务, delay表示在多长时间之后执行该任务
            @Override
            public void run() {
                System.out.println("hello world 3");
            }
        }, 3000);

        timer.schedule(new TimerTask() {  // Timer也可以安排多个任务
            @Override
            public void run() {
                System.out.println("hello world 2");
            }
        }, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello world 1");
            }
        }, 1000);
        System.out.println("程序开始运行");
    }
}

 上述代码, 由于hello1 延时1000ms打印, hello2延时2000ms打印, hello3延时3000ms打印, 所以打印顺序应该是hello1 -> hello2 -> hello3 

二. 定时器的实现

了解了定时器的基本用法之后, 接下来我们就自己具体实现一下定时器吧~

实现一个定时器, 需要完成以下几个重要的步骤:

(1) 创建类, 描述一个要执行的任务 (任务的内容, 任务执行的时间).

(2) 管理多个任务: 通过一定的数据结构, 把多个任务管理起来.

(3) 指定专门的线程, 执行这里的任务.

import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable runnable;
    private long time;
    public MyTimerTask(Runnable runnable, long delay) {  //runnable->要执行的任务, delay->延时时间
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
        // System.currentTimeMillis() --> 当前的时间戳.
    }
    public void run() {
        runnable.run();
    }
    public long getTime() {
        return time;
    }
    @Override
    public int compareTo(MyTimerTask o) {
        //此处减的顺序就决定了是大堆还是小堆 (我们这里需要小堆)
        // 谁减谁才能得到小堆? -> 实验(尝试)的方式
        return (int) (this.time - o.time);
    }
}
class MyTimer {
    //private List<MyTimerTask> list = new ArrayList<>();
    // 这里使用list保存任务不是一个很好的选择. 如果用list保存任务,
    // 后续我们执行列表中的任务的时候, 就需要依次遍历每个元素, 任务执行完毕之后, 还需要把对应的任务从list中删除掉.
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    // 此时这里的TimerTask还需要指定比较规则.(实现comparable接口 / 指定comparator)
    
    public MyTimer() {
        // 创建线程, 执行上述队列中的内容.
        Thread t = new Thread(() -> {
            while (true) {
                if (queue.isEmpty()) { //如果队列为空, 无法取出任务. continue
                    continue;
                }
                MyTimerTask current = queue.peek(); //取出队首元素
                if (System.currentTimeMillis() >= current.getTime()) {
                    //执行任务
                    current.run();
                    //删除执行过的任务
                    queue.poll();
                } else {
                    //时间没到, 不执行任务
                    continue;
                }

            }
        });

        t.start();
    }
    public void schedule(Runnable runnable, long delay) {
        MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
        queue.offer(myTimerTask);
    }

}

显然, 上述代码没有针对线程安全问题作出处理.

import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable runnable;
    private long time;
    public MyTimerTask(Runnable runnable, long delay) {  /
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
        // System.currentTimeMillis() --> 当前的时间戳.
    }
    public void run() {
        runnable.run();
    }
    public long getTime() {
        return time;
    }
    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}
class MyTimer {
    private Object locker = new Object();
    public MyTimer() {
        // 创建线程, 执行上述队列中的内容.
        Thread t = new Thread(() -> {
            while (true) {
               synchronized (locker) {
                   if (queue.isEmpty()) { //如果队列为空, 无法取出任务. continue
                       continue;
                   }
                   MyTimerTask current = queue.peek(); //取出队首元素
                   if (System.currentTimeMillis() >= current.getTime()) {
                       //执行任务
                       current.run();
                       //删除执行过的任务
                       queue.poll();
                   } else {
                       //时间没到, 不执行任务
                       continue;
                   }

               }
            }
        });

        t.start();
    }
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
            queue.offer(myTimerTask);
        }
    }
}

当初始情况下,队列为空时, 经过 if 判定, 就会执行continue, 在执行while循环, 再if判定, 还是空, 再continue, 这样的话, 当前线程就一直在循环做一些没有意义的事情, 而且还占用着锁资源.  所以, 我们这里执行wait()是一个更好的选择. 在这里wait(), 就需要在queue加入元素之后唤醒(notify). 我们在前一篇文章阻塞队列那里是说过, wait()外的循环使用while更好一些, 所以我们这里把if换成while.

假设队列中已经包含元素了, 但是执行时间还没到, 那么 if 判定不满足, 就会执行else, 执行里面的continue, 然后在while(true)进来, 再执行到 if 判定, 仍不满足, 再执行else, 继续continue, 这样一来, 这里就跟上面的那种情况一样了, 在循环做一些没有意义的事情, 而且占用着锁资源. 那么如何解决这样的问题呢? --> 还是使用wait(), 不过这里wait()不用notify唤醒, 而是使用超时时间. 等待了指定时间之后自动唤醒.  注意这里使用wait()而不能使用sleep(), 因为如果在sleep过程中来了一个执行时间更早的任务, 那么sleep不会唤醒, 但是wait()就会被唤醒. 而且sleep在休眠的时候不会释放锁资源, 它是"抱着锁"睡的, 这样的话其他线程想拿锁资源就无法拿到了.

这里我们不使用java提供的BlockingQueue而是自己加锁的原因是: 如果使用BlockingQueue, 那么它的take()方法和wait()带锁, 这里就出现两把锁了, 就很有可能导致死锁问题的出现.

import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable runnable;
    private long time;
    public MyTimerTask(Runnable runnable, long delay) {  
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
        // System.currentTimeMillis() --> 当前的时间戳.
    }
    public void run() {
        runnable.run();
    }
    public long getTime() {
        return time;
    }
    @Override
    public int compareTo(MyTimerTask o) {

        return (int) (this.time - o.time);
        
    }
}
class MyTimer {
    
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    // 此时这里的TimerTask还需要指定比较规则.(实现comparable接口 / 指定comparator)

    private Object locker = new Object();
    public MyTimer() {
        // 创建线程, 执行上述队列中的内容.
        Thread t = new Thread(() -> {
            try{
                while (true) {
                    synchronized (locker) {
                        while (queue.isEmpty()) { //如果队列为空, 无法取出任务. continue
                            //continue;
                            locker.wait();
                        }
                        MyTimerTask current = queue.peek(); //取出队首元素
                        if (System.currentTimeMillis() >= current.getTime()) {
                            //执行任务
                            current.run();
                            //删除执行过的任务
                            queue.poll();
                        } else {
                            //时间没到, 不执行任务
                            //continue;
                            locker.wait(current.getTime() - System.currentTimeMillis());
                            // 用任务执行时间减去当前时间, 得到一个时间差, wait就等待这么长时间. 如果到点了就唤醒.
                        }

                    }
                }
            }catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        t.start();
    }
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            MyTimerTask myTimerTask = new MyTimerTask(runnable, delay);
            queue.offer(myTimerTask);
            locker.notify();
        }
    }
}

那么以上就是修改完全之后的最终代码了~

我们可以在main方法中试一下看它是否和系统提供的定时器功能一样:

public class Demo32 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() { // 可以重写run方法指定任务
            @Override
            public void run() {
                System.out.println("hello 3000");
            }
        }, 3000);
        myTimer.schedule(() -> { //也可以使用lambda表达式
            System.out.println("hello 2000");
        },2000);
        myTimer.schedule(()-> {
            System.out.println("hello 1000");
        }, 1000);
    }
}

执行结果没有问题~ 

定时器除了用优先级队列的方式实现, 还有一种经典的实现方式 -> 时间轮. 这里我们就再不做讨论了.


原文地址:https://blog.csdn.net/2301_80313139/article/details/143700896

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!