Timer 原理 到 spring 定时器任务
Java Timer 怎么实现延时任务的?怎么实现周期任务的?消耗资源多吗?执行时间准确吗?
1 Timer 的基本使用。
第一个参数是任务,第二个参数可以是指定时间,第三个参数如果指定了就会周期的执行这个任务
2 Timer 的原理
概述:Timer 有一个内部线程,和一个阻塞队列,在Timer 执行下一个任务之前会wait指定时间,在Timer 里面阻塞队列是空的时候会无限期wait,然后再再新加入任务(2分查找找到插入位置)的以后弹出需要最近执行的一个任务,并且唤醒阻塞的循环消费任务的线程。在循环消费任务的线程中,如果任务是周期任务那么就计算出下一个执行时间点,然后把它插入到按照时间排序的队列中(插入过程使用2分查找),然后判断当前任务是否到了激活时间,如果到了就执行,如果没到就睡到指定时间。
源码解析:
1 添加任务以后判断了延时时间不能是负数,然后调用 sched
2 sched 方法,做了一些判断断,然后拿到任务队列的锁,然后阻塞用队列的添加方法,添加完成以后,判断如果当前新加入的任务是排最前面的,那么就唤醒任务线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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(); } } |
3 阻塞队列的添加方法,判断是否需要拓容,通过二分查找下一次执行时间,找应该插入的位置
1 2 3 4 5 6 7 8 | void add(TimerTask task) { // Grow backing store if necessary if (size + 1 == queue.length) queue = Arrays.copyOf(queue, 2 *queue.length); queue[++size] = task; fixUp(size); } |
1 2 3 4 5 6 7 8 9 | private void fixUp( int k) { while (k > 1 ) { int j = k >> 1 ; if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime) break ; TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; k = j; } } |
4循环任务,循环过程,如果任务队列是空就等着(直到添加元素后唤醒它),醒了 以后取出最上面的任务,获取任务锁,如果任务取消,直接下跳过,比对当前时间和执行时间,判断任务是否激活,没激活就等到需要激活的时间,
如果激活了,如果是普通任务,直接从队里里面删除,修改执行状态,如果是循环任务,那么计算出下次执行时间,并且在队列里面重排序这个任务,最后调用这个任务 run 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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)) { 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); } } } 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) { } } } |
3 什么情况下Timer 应该避免怎么用
因为一个Timer 里面有一个 线程,如果我们有多个延时任务,多种不同周期的任务时,我们应该考虑使用少量或者一个TImer,而不是每种甚至每个任务都创建一个Timer,避免大量资源浪费在多线程上下文切换只上。
4 spring @Scheduled 的定时任务和 实现了 SchedulingConfigurer 的 定时任务
他们都支持 cron ,这种定时器原理其实类似于Timer 周期执行,在执行当前任务的时候通过 cron 计算出 下一次执行的时间,然后插入到 任务队列的指定位置就行了。使用 SchedulingConfigurer自定义定人任务的时候同样 要避免太过大量的创建 SchedulingConfigurer 的个数,避免太多线程上下文切换带来的消耗。
能耍的时候就一定要耍,不能耍的时候一定要学。
--天道酬勤,贵在坚持posted on 2022-05-24 21:55 zhangyukun 阅读(176) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构