Android Timer(定时器)踩坑记

开发者 2024-9-6 10:55:54 70 0 来自 中国
配景

由于网络需求须要通过发心跳来维持毗连的创建,以是客户端须要通过计时器,每隔断肯定变乱发一次心跳哀求到服务器,以此到达毗连保活。我用了Timer来举行定时任务后,服务端童鞋找我说为啥同一秒会有重复的心跳哀求发到服务器上呢?这就延伸出我们本日文章所要讲的内容了。
题目

业务场景是每隔10秒上报一次ping心跳,当09:50:33时间Timer实验了一次ping的上报任务后,下一次的上报的时间却是在09:50:54举行ping上报了(此次ping上报出现重复上报题目),中心隔断20几秒,在排查并非代码逻辑题目,把目光投向了定时器自身题目。
分析题目

联合自身日志和Timer的源码阅读,可以知道此题目是由于利用Timer举行定时任务上报,当你的app的cpu资源竞争非常猛烈时间,你的Timer内里的Thread没有办法定时获取cpu资源来实验开辟者须要做的定时任务,当获取到cpu资源时,Timer就会为了补充之前漏实验的定时任务,会在同一时候举行1-n次的定时任务。
前置知识

刚入门口试的我们,多多少少都会被口试官问到sleep和wait的区别,当初的我们涉世尚浅,并不是太多关注这两个的区别,以为并没有什么用处,但看完我这篇文章你就明白当初口试官为什么问你这个题目了。这里先大概讲下,wait是让当火线程让出体系资源,开释锁,处于线程队列中举行期待;sleep是不让出体系资源,当火线程挂起肯定时间,不开释锁。Timer内里源码的实现就是用了wait实现。
源码分析


  • 起首是从Timer的schedule函数开始看起来,各人对于这三个参数应该都有肯定的熟悉,我这里就不睁开细讲了。重要看的是scheduleAtFixedRate函数里的sched调用。注意sched第二个参数是当前体系时间+开辟者所需的delay时间。
Timer().scheduleAtFixedRate(object : TimerTask() {            override fun run() {                ……            }        }, delayMills, periodMills)public void scheduleAtFixedRate(TimerTask task, long delay, long period) {        ……        sched(task, System.currentTimeMillis()+delay, period); }

  • sched方法重要是把Timer的启动时间和隔断存储到Task对象里,再把Task对象加到队列里,看完了Timer的构造,我们下面看下Timer是怎样运行。
private void sched(TimerTask task, long time, long period) {       ……        synchronized(queue) {          ……            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();        }    }

  • Timer内部有个TimerThread线程,Run内部实现为一个死循环,通过wait/wait(time)/notify 实现挂起/唤醒利用。在mainLoop内里有个逻辑缺陷就是,每次当火线程获取cpu资源时间,就会判断队列头部的Task是否到时间实验。假如未到时间,则wait剩余时间;假如到时间实验,则更新Task的下一次实验的时间(nextExecutionTime)。
注意:那么题目就出现了,假如你的定时器任务实验完后,wait了下一次隔断时间,但是谁人时间段cpu资源竞争很猛烈,TimerThread根本抢不到cpu资源去实验,当到达下下一次隔断时间获取到cpu的资源时间,你的死循环就因为currentTime - executionTime >= 2倍的隔断时间,以是会同一时候实验两个Runnable的回调,自然你Runnable回调也会在同一时候做出重复的举动。
class TimerThread extends Thread {  public void run() {        ……        mainLoop();        ……    }}private void mainLoop() {        while (true) {            try {                TimerTask task;                boolean taskFired;                synchronized(queue) {                    // 当Task队列为空时间,挂起体系资源,期待notify的唤醒                    while (queue.isEmpty() && newTasksMayBeScheduled)                        queue.wait();                    ……                    // 从队列中取出头部Task                    task = queue.getMin();                    synchronized(task.lock) {                       ……                        currentTime = System.currentTimeMillis();                        //Task的实验sched函数时的体系时间                        executionTime = task.nextExecutionTime;                        //taskFired:true 实验时间到了,false 实验时间未到                        if (taskFired = (executionTime<=currentTime)) {                            if (task.period == 0) { // Non-repeating, remove                                queue.removeMin();                                task.state = TimerTask.EXECUTED;                            } else { // Repeating task, reschedule                                //更新头部Task的nextExecutionTime时间                                queue.rescheduleMin(                                  task.period<0 ? currentTime   - task.period                                                : executionTime + task.period);                            }                        }                    }                    if (!taskFired) // 任务还没有到时实验,挂起剩余的时间                        queue.wait(executionTime - currentTime);                }                if (taskFired)  // 任务到时实验,回调Runnable                    task.run();            } catch(InterruptedException e) {            }        }    }总结


  • Timer的设计者也思量到多报的环境,以是设计了假如你传进来的period为负数,就用当前体系时间+你的period隔断时间,从而选择漏报而不是多报一次,但是好像尚有bug,以是外貌的schedulexxx只要period为负数就会抛非常。
  • 全部跑线程的任务都会有资源竞争的题目,假如想要办理此类题目,应该规划线程优先级,业务的优先级最多到哪个品级,上报、crash等线程优先级比业务品级高。只有明白线程品级,才气包管你的线程能按时获取cpu资源实验任务。
  • 一起积极搬砖?
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 18:23, Processed in 0.178852 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表