延时队列-Timer

Posted a-yes

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了延时队列-Timer相关的知识,希望对你有一定的参考价值。

Timer

1. 使用方法

public class TimerTest {
    public static void main(String[] args) {
        Timer timer = new Timer(); 
        System.out.format("name:{%s},time:{%s},thread:{%s}
", "start", new Date(), Thread.currentThread());

        timer.schedule(new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.format("name:{%s},time:{%s},thread:{%s}
", "5s", new Date(), Thread.currentThread());
                Thread.sleep(5000);
            }
        }, 1000);
        timer.scheduleAtFixedRate(new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                System.out.format("name:{%s},time:{%s},thread:{%s}
", "5s2", new Date(), Thread.currentThread());
                Thread.sleep(5000);
            }
        }, 2000);  
    }
}

2. 源码解析

构造方法

    //默认线程名
 public Timer() {
        this("Timer-" + serialNumber());
    }

    //是否有守护线程
    public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
    }
 //自定义线程名称
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }
 //自定义名称和设置是否有守护线程
    public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
    }

构造方法中的thread是Timer的内部类, TimerThread extends Thread,构造方法启动线程 , TimerThread持有一个小顶堆队列,TaskQueue

TimerThread extends Thread

 public void run() {
        try {
            //主要逻辑放在mainLoop中
            mainLoop();
        } finally {
            // Someone killed this Thread, behave as if Timer cancelled
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  // Eliminate obsolete references
            }
        }
    }

    /**
     * The main timer loop.  (See class comment.)
     */
    private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // 如果队列为空且执行状态正常则等待
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    //获取到期时间最短的任务
                    task = queue.getMin();
                    synchronized(task.lock) {
                        //如果任务取消,则弹出继续
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        //当前时间
                        currentTime = System.currentTimeMillis();
                        //任务的下次执行时间
                        executionTime = task.nextExecutionTime;
                        
                        //如果执行时间到期了
                        if (taskFired = (executionTime<=currentTime)) {
                            //如果不需要重复,则把此任务弹出,并把状态设为已执行
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                //需要重复
                                // 由设定任务可知,task.period<0的都是 schedule 方法
                                // 当是schedule方法时,下次执行时间为当期时间+间隔时间
                                // 当是scheduleAtFixedRate方法时,下次执行时间为 任务中上次计算出的下次执行时间+间隔时间
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn‘t yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                //执行任务
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }

设定任务

一共有6个方法,

    //延迟一定时间后执行一次
 public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }

 //在指定时间执行一次
    public void schedule(TimerTask task, Date time) {
        sched(task, time.getTime(), 0);
    }

 //延迟一定时间后执行,并间隔一定时间重复执行
    public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
    }

 //在指定时间执行一次,并间隔一定时间重复执行
    public void schedule(TimerTask task, Date firstTime, long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }

 //延迟一定时间后执行,并间隔一定时间重复执行(与)
    public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, period);
    }

 //在指定时间执行一次,并间隔一定时间重复执行
    public void scheduleAtFixedRate(TimerTask task, Date firstTime,
                                    long period) {
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), period);
    }
?

schedule与scheduleAtFixedRate主要区别是,如果执行的任务耗时超过了时间间隔,下次的执行时间,schedule是以当前时间+时间间隔计算下次执行时间,scheduleAtFixedRate是以上次计算出的下次执行时间+时间间隔计算下次执行时间

?

可以看到其实这6个方法都是调用的 sched(TimerTask task, long time, long period)方法,

    private void sched(TimerTask task, long time, long period) {
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");

        // Constrain value of period sufficiently to prevent numeric
        // overflow while still being effectively infinitely large.
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;

        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");

            synchronized(task.lock) {
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                        "Task already scheduled or cancelled");
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
   //任务加入小顶堆
            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }

举例

举例说明scheduleAtFixedRate和schedule区别

设置一个定时任务,每个5秒重复一次,第一次执行耗时6秒,之后每次执行耗时3秒.

采用schedule方法,则第二次执行时间为第一次执行结束后+5s , 即第11秒执行第二次

采用scheduleAtFixedRate方法,则第二次执行时间为第一次重复时间+5s,即第10秒执行第二次,

可以看出schedule方法保证的是前一个任务结束时间和后一个任务开始时间的间隔,scheduleAtFixedRate保证的是任意两个任务开始时间的间隔。

3. 总结

  • Timer只有一个单线程在轮训任务队列
  • Timer使用synchronized关键字保证线程安全
  • Timer可以在任务里使用线程进行耗时操作,防止耗时太久影响下个任务执行
  • 当有任务抛出RuntimeException异常时,所有任务都会停止,注意执行任务的异常捕获

本文使用 mdnice 排版

以上是关于延时队列-Timer的主要内容,如果未能解决你的问题,请参考以下文章

Flutter延时任务Flutter通过Future与Timer实现延时任务

ScheduledExecutorService延时线程池的简单使用

环形队列-高效定时触发

Java两种延时——thread和timer

redis实现延时队列(附完整代码)

ScheduledThreadPoolExecutor周期任务或延时任务线程池