JavaWeb~教你如何自己实现一个定时器

什么是定时器

  • 定时器是多线程编程中的一个重要的组件
  • 好比一个闹钟 定好一个时间让一个线程去执行
  • 定时器在网络编程中特别常见

实现定时器需要什么

  1. 需要一个Task类来描述一段逻辑或者说是一个要执行的任务 同时要记录这个任务在啥时候执行
// 优先队列中的元素必须是可比较的~
    // 比较规则的指定主要是两种方式:
    // 1. 让 Task 实现 Comparable 接口
    // 2. 让优先队列构造的时候, 传入一个比较器对象 (Comparator).
    static class Task implements Comparable<Task> {
        // Runnable 中有一个 run 方法, 就可以借助这个 run 方法来描述要执行的具体的任务是啥.
        private Runnable command;
        // time 表示啥时候来执行 command, 是一个绝对时间(ms级别的时间戳)
        private long time;

        // 构造方法的参数表示: 多少毫秒之后执行. (相对时间) 这个相对时间的参数是为了用起来方便
        public Task(Runnable command, long after) {
            this.command = command;
            this.time = System.currentTimeMillis() + after;
        }

        // 执行任务的具体逻辑
        public void run() {
            command.run();
        }

        @Override
        public int compareTo(Task o) {
            // 谁的时间小(先执行)
            return (int) (this.time - o.time);
        }
    }
  1. 需要使用一个阻塞优先级队列组织所有的任务 判断谁先执行 谁后执行
static class Timer {
        // 为了避免忙等, 需要使用 wait 方法.
        // 使用一个单独的对象来辅助进行 wait
        // 使用 this 也行.
        private final Object mailBox = new Object();

        // 定时器的基本构成, 有三个部分.
        // 1. 用一个类来描述 "任务"
        // 2. 用一个阻塞优先队列来组织若干个任务. 让队首元素就是时间最早的任务.
        //    如果队首元素时间未到, 那么其他元素也肯定不能执行.
        private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        // 3. 用一个线程来循环扫描当前的阻塞队列的队首元素, 如果时间到, 就执行指定的任务.
        public Timer() {
            // 创建线程
            Worker worker = new Worker(queue, mailBox);
            worker.start();
        }
        // 4. 还需要提供一个方法, 让调用者能把任务给 "安排" 进来.
        //    schedule => 安排
        public void schedule(Runnable command, long after) {
            Task task = new Task(command, after);
            queue.put(task);
            synchronized (mailBox) {
            //防止新加进来的任务执行优先级是最高的 就去唤醒等待中的线程 让其去执行优先级最高的任务
                mailBox.notify();
            }
        }
    }
  1. 还需要一个扫描线程 循环扫描判断队列的队首元素是不是到时间了需要执行了 如果需要执行了就执行该任务
 static class Worker extends Thread {
        private PriorityBlockingQueue<Task> queue;
        private final Object mailBox;

        public Worker(PriorityBlockingQueue<Task> queue, Object mailBox) {
            this.queue = queue;
            this.mailBox = mailBox;
        }

        @Override
        public void run() {
            // 实现具体的线程执行的内容
            while (true) {
                try {
                    // 1. 取出队首元素, 检查时间是否到了
                    Task task = queue.take();
                    // 2. 检查当前任务时间是否到了
                    long curTime = System.currentTimeMillis();
                    if (task.time > curTime) {
                        // 时间还没到~, 就把任务再塞回队列中
                        queue.put(task);
                        synchronized (mailBox) {
                        //为了避免忙等 就直接让该线程等待到时间到 然后去自动唤醒
                            mailBox.wait(task.time - curTime);
                        }
                    } else {
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }

完整代码+测试

import java.util.concurrent.PriorityBlockingQueue;

public class Timer {

    //添加一个公共用对象去实现wait和唤醒
    private final Object loker = new Object();
    //首先需要一个类Task去描述一个任务
    //使用阻塞优先级队列来组织若干个线程判断谁先执行
    private PriorityBlockingQueue<Task> blockingQueue = new PriorityBlockingQueue<>();

    //用一个线程去循环扫描当前队列的队首元素 如果时间到了就执行该任务
    public Timer() {
        Worker worker = new Worker(blockingQueue, loker);
        //启动worker里的线程
        worker.start();
    }

    //计时器还需要一个方法让用户往里放任务
    public void schedule(Runnable runnable, long time) {
        Task task = new Task(runnable, time);
        blockingQueue.put(task);

        //新进来任务要唤醒wait的线程让优先级队列重新组织去判断哪个先执行
        synchronized (loker) {
            loker.notify();
        }
    }
}


//因为要放到优先级阻塞队列所有要实现Compararble接口
class Task implements Comparable<Task> {
    //在这类去描述一个线程任务 所以他需要你个Runnable和time去确定什么时候执行
    private Runnable runnable;
    private long time;

    public Task(Runnable runnable, long after) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + after;
    }

    //提供一个run方法执行Runnable
    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(Task o) {
        //要让时间小的先去执行
        return (int) (this.time - o.time);
    }
}

//执行任务的一个类
class Worker extends Thread {

    //要接收计时器传来的阻塞优先级队列
    private PriorityBlockingQueue<Task> blockingQueue;
    private final Object loker;

    public Worker(PriorityBlockingQueue<Task> blockingQueue, Object loker) {
        this.blockingQueue = blockingQueue;
        this.loker = loker;
    }

    @Override
    public void run() {

        //循环扫描 如果时间到了就执行任务 时间未到就等待
        while (true) {
            try {
                Task task = blockingQueue.take();
                long curTime = System.currentTimeMillis();
                if (task.getTime() > curTime) {
                    //时间未到继续等待
                    blockingQueue.put(task);

                    //为了避免忙等我们使用wait来提高效率 但是在等待过程中如果又加进来优先级更高的任务 就要先执行那个任务 所以就需要换醒这个线程
                    synchronized (loker) {
                        loker.wait(task.getTime() - curTime);
                    }
                } else {
                    //时间到了执行任务
                    task.run();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}

  • 测试
public class TestTimer {
    public static void main(String[] args) {

        Timer timer = new Timer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("haha");
                 //每一个线程有一个任务就是再次去创建一个线程
                timer.schedule(this, 2000);
            }
        }, 2000);
    }
}

  • 结果便是每俩秒输出一个haha
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Shangxingya/article/details/106742627