【多线程】(四)定时器及其模拟实现

文章目录

  • 一、什么是定时器
  • 二、Java库中的定时器
    • 2.1 Timer 类
    • 2.2 使用案例
  • 三、定时器的模拟实现


一、什么是定时器

定时器是一种用于在指定的时间间隔或指定的时间点执行任务的工具。它允许开发人员在程序中安排任务在未来的某个时间点执行,或者按照固定的时间间隔重复执行。

定时器通常用于需要定期执行某些操作的场景,例如定时任务调度、定时数据备份、定时数据同步等。它可以帮助开发人员实现按照预定时间执行任务的逻辑,而不需要手动计算时间和编写复杂的定时逻辑。

二、Java库中的定时器

2.1 Timer 类

在Java中,定时器功能可以通过java.util.Timer类来实现。Timer类提供了一组方法,可以安排任务在指定的时间点执行,或按照固定的时间间隔重复执行。使用Timer类,开发人员可以创建一个定时器对象,然后通过调用schedule方法来安排任务执行。

下面是Timer类常用方法的Markdown表格表示:

方法名 描述
void schedule(TimerTask task, Date time) 在指定的时间点执行任务。
void schedule(TimerTask task, Date firstTime, long period) 在指定的时间点开始执行任务,并以指定的时间间隔重复执行。
void schedule(TimerTask task, long delay) 在指定的延迟时间后执行任务,只执行一次。
void schedule(TimerTask task, long delay, long period) 在指定的延迟时间后开始执行任务,并以指定的时间间隔重复执行。

这些方法都是Timer类提供的重载方法,用于安排定时任务的执行。其中,TimerTask是一个抽象类(功能相当于Runnable),需要通过继承和实现其run方法来定义具体的任务逻辑。

注意,在使用Timer类时,需要注意任务的执行时间和时间间隔,以避免任务执行时间过长导致后续任务延迟执行或任务堆积。同时,还需要考虑线程安全和异常处理,以保证定时任务的可靠性和稳定性。

2.2 使用案例

public class ThreadDemo {

    public static void main(String[] args) {
        Timer timer = new Timer();

        // TimerTask 就相当于 Runnable
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("运行定时器任务3");
            }
        }, 3000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("运行定时器任务2");
            }
        }, 2000);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("运行定时器任务1");
            }
        }, 1000);

    }
}

这段代码演示了使用Timer类来安排多个定时任务的执行。

  • 在这个示例中,首先创建了一个Timer对象。然后,通过timer.schedule()方法安排了三个定时任务,分别在不同的延迟时间后执行。

  • 每个定时任务都是一个TimerTask的匿名内部类对象,重写了run()方法来定义任务的逻辑。在run()方法中,分别输出不同的消息表示任务的执行。

  • 第一个定时任务将在程序启动后的1秒后执行,输出消息"运行定时器任务1"。第二个定时任务将在程序启动后的2秒后执行,输出消息"运行定时器任务2"。第三个定时任务将在程序启动后的3秒后执行,输出消息"运行定时器任务3"。

三、定时器的模拟实现


import java.util.concurrent.PriorityBlockingQueue;

// 要执行的任务类
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(MyTask o) {
        return (int) (this.time - o.time);
    }
}

// 模拟实现定时器
class MyTimer {
    // 扫描线程
    private Thread thread = null;

    // 使用优先级阻塞队列来保存任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public MyTimer() {
        thread = new Thread(() -> {
            // 取出队首元素,检查队首元素是否到时间了,
            // 如果时间没到,就把任务放回队列
            // 否则就取出来执行
            while (true) {
                try {
                    // 保证wait之后再执行put中的notify操作,
                    // 目的是防止因为线程的无序调度导致还没有执行wait的时候就notify了。
                    // 比如现在13点,当前任务14点执行,当执行完第58行代码时,当前线程被切走了,
                    // 然后插入了一个13点30执行的任务并执行了notify操作,而此时还没有wait
                    synchronized (this) {
                        MyTask task = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime < task.getTime()) {
                            // 还没到点,先不执行,将任务放回队列
                            queue.put(task);

                            // 然后使用 wait 进行等待,当新放入一个元素的时候就notify。目的是解决忙等问题。
                            // 不使用sleep的原因就是,中途可能放入一个更早的任务
                            this.wait(task.getTime() - curTime);

                        } else {
                            // 到点了,执行任务
                            task.run();
                        }
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start();
    }

    /**
     * @param runnable 任务内容
     * @param after    在多少毫秒之后执行任务
     */
    public void schedule(Runnable runnable, long after) {
        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
        queue.put(task);
        // 放入一个任务就通知
        synchronized (this) {
            this.notify();
        }
    }
}

这段代码是一个简单的模拟实现定时器的示例。它包含了一个MyTimer类和一个MyTask类。

  • MyTask类表示要执行的任务,实现了Comparable接口用于在优先级队列中进行比较。它包含一个runnable属性表示任务内容,一个time属性表示任务的执行时间。在MyTask类中,定义了getTime()方法用于获取任务的执行时间,以及run()方法用于执行任务的具体逻辑。

  • MyTimer类模拟实现了定时器的功能。它使用PriorityBlockingQueue作为优先级阻塞队列来保存任务。在MyTimer类的构造函数中,创建了一个扫描线程,该线程不断地从队列中取出任务进行处理。

  • 在扫描线程的循环中,首先使用queue.take()方法从队列中取出队首元素(最近的任务)。然后,获取当前时间,如果当前时间小于任务的执行时间,则将任务放回队列,并使用wait()方法进行等待。通过wait()方法,等待到任务的执行时间或有新任务插入队列时才被唤醒,目的是避免线程的忙等。

  • 如果当前时间大于等于任务的执行时间,说明任务到达执行时间,就执行该任务的run()方法。

  • MyTimer类提供了一个schedule()方法,用于向定时器中添加任务。在schedule()方法中,根据传入的参数创建一个MyTask对象,并将其放入队列中。同时,通过synchronized关键字加锁,并使用notify()方法通知正在等待的扫描线程。

  • 通过创建MyTimer对象,可以使用schedule()方法来安排定时任务的执行。定时任务会按照指定的时间间隔被添加到队列中,并在到达执行时间时被执行。

你可能感兴趣的:(Java进阶,java,定时器)