java定时器之Timer使用与原理分析[通俗易懂]

java定时器之Timer使用与原理分析[通俗易懂]Timer和TimerTaskTimer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。【使用举例】【schedule(TimerTasktask,longdelay)延迟delay毫秒执行】…

大家好,又见面了,我是你们的朋友全栈君。

Timer和TimerTask

Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。

TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。

【使用举例】

【schedule(TimerTask task, long delay) 延迟 delay 毫秒 执行】

public static void main(String[] args) {
        for (int i = 0; i < 10; ++i) {
            new Timer("timer - " + i).schedule(new TimerTask() {
                @Override
                public void run() {
                    println(Thread.currentThread().getName() + " run ");
                }
            }, 1000);
        }
    }

【schedule(TimerTask task, Date time) 特定时间执行】

public static void main(String[] args) {
        for (int i = 0; i < 10; ++i) {
            new Timer("timer - " + i).schedule(new TimerTask() {
                @Override
                public void run() {
                    println(Thread.currentThread().getName() + " run ");
                }
            }, new Date(System.currentTimeMillis() + 2000));
        }
    }

【schedule(TimerTask task, long delay, long period) 延迟 delay 执行并每隔period 执行一次】

public static void main(String[] args) {
        for (int i = 0; i < 10; ++i) {
            new Timer("timer - " + i).schedule(new TimerTask() {
                @Override
                public void run() {
                    println(Thread.currentThread().getName() + " run ");
                }
            }, 2000 , 3000);
        }
    }

【原理分析】

timer底层是把一个个任务放在一个TaskQueue中,TaskQueue是以平衡二进制堆表示的优先级队列,他是通过nextExecutionTime进行优先级排序的,距离下次执行时间越短优先级越高,通过getMin()获得queue[1]

并且出队的时候通过synchronized保证线程安全,延迟执行和特定时间执行的底层实现类似,源码如下:

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();
        }
    }

我们主要来看下周期性调度通过什么方式实现的,我们直接来分析源码如下:

private void mainLoop() {
  // 首先一直监听队列中有没有任务
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
    // 同步,保证任务执行顺序
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    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)) {
       // 到这里是延迟执行和特定时间点执行已经结束了,状态标记为EXECUTED,周期性执行继续往下走
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
        // 这里他又重新计算了下下个任务的执行,并且任务还在队列中
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
     // 如果任务执行时间大于当前时间说明任务还没点,继续等,否则执行run代码块
                    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) {
            }
        }
    }
}

这种定时器适合单点或者多台同时执行互不影响的场景

【缺点】

1、首先Timer对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。

2、其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止,同时Timer也不会重新恢复线程的执行,他会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执行的TimerTask也不会再执行了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为

3、Timer在执行定时任务时只会创建一个线程任务,如果存在多个线程,若其中某个线程因为某种原因而导致线程任务执行时间过长,超过了两个任务的间隔时间,会导致下一个任务执行时间滞后

这些缺点可以通过ScheduledExecutorService来代替

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/156801.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • android telephony 原理解析与开发指南_Android逆向pdf

    android telephony 原理解析与开发指南_Android逆向pdf目录:Phone的继承关系与PhoneFactory(GsmCdmaPhone、ImsPhone、SipPhone)Phone进程的启动Phone对象的初始化(DefaultPhoneNotif

  • 如何卸载干净JAVA?「建议收藏」

    如何卸载干净JAVA?「建议收藏」有很多小伙伴下载了JAVA的JDK(java开发工具包)并安装成功运行后,发现自己下错了版本。凉了,半天白搞了。卸载之后又发现在再安装出现安装不了的问题。这往往是因为JAVA并没有卸载完全。今天我们就看看如何完全卸载JAVA。JAVA卸载有两种方式。手动和用JAVA卸载工具。第一种,手动。1.打开控制面板,找到卸载程序,在找到java的程序,并卸载。2.这样之后,java虽然看不见了。但是还没有卸载干净。打开命令行窗口,输入命令regited。打开注册表窗口,删除ja…

  • maven阿里云仓库配置_阿里docker仓库

    maven阿里云仓库配置_阿里docker仓库maven仓库1、阿里云maven仓库https://mvnrepository.com/artifact/com.aliyun2、仓库配置第一步:修改maven根目录下的conf文件夹中的setting.xml文件,内容如下:<mirrors><mirror><id>alimaven</id>&lt…

    2022年10月31日
  • RPM卸载 (Linux 使用)[通俗易懂]

    RPM卸载 (Linux 使用)[通俗易懂]可以先用rpm-q’xxx’或者rpm-qf’xxx/bin/xxxx.xx’来查询一下所属的rpm包的名字。然后用rpm-e’xxxxxx’来删之。’xxx/bin/xxxx.xx’是一个包中任意的文件’xxxxxx’是查询得到的rpm包的名称rpm-e的时候后面的文件名不用加版本号详细说明:安全地卸载RPM卸载软件包,并不是简单地将原来安

  • HDU 3788 和九度OJ 1006测试数据是不一样的

    HDU 3788 和九度OJ 1006测试数据是不一样的

  • 三张图搞透第一范式(1NF)、第二范式(2NF)和第三范式(3NF)的区别

    三张图搞透第一范式(1NF)、第二范式(2NF)和第三范式(3NF)的区别第一范式:  列1唯一确定列2、列3、列4…,即列不能再分成其它几列。  假设列1:订单名,列2:商品名,一个订单名里可以有多个商品名,所以这样就不符合第一范式。第二范式:  首先符合1NF,另外包含两部分内容,一是表必须有一个(及以上)主键;二是没有包含在主键中的列必须全部依赖于全部主键,而不能只依赖于主键的一部分而不依赖全部主键。  定义听起来有点绕,不慌,直接看图,只有全部的非…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号