理解android中的Timer

前言

源碼大家可以到這里搜索下載

正文

Timer內(nèi)部有2個(gè)比較重要的成員變量

    private final TaskQueue queue = new TaskQueue();
    private final TimerThread thread = new TimerThread(queue);

一個(gè)是任務(wù)隊(duì)列,一個(gè)是執(zhí)行任務(wù)的線程。我們線看下這個(gè) TimerThread,它繼承自Thread,并聲明了一個(gè)mainLoop方法,當(dāng)點(diǎn)用 thread.start后,它就會(huì)執(zhí)行這個(gè)方法

    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) {
            }
        }
    }

我們看這個(gè)方法,典型的就是一個(gè)生產(chǎn)者消費(fèi)者的模型。
如果queue里沒(méi)有數(shù)據(jù),那么它就通過(guò)點(diǎn)用wait方法釋放鎖,等待被生產(chǎn)者喚醒。當(dāng)方法再次被喚醒的時(shí)候,代碼先去檢查queue是不是空的,是空的就繼續(xù)wait,否則的話,通過(guò)queue的getMin函數(shù)獲取一個(gè)任務(wù)task,
如果當(dāng)前時(shí)間大于等于task的執(zhí)行時(shí)間:
說(shuō)明該任務(wù)該執(zhí)行了,如果當(dāng)前任務(wù)不是重復(fù)任務(wù),那么直接把任務(wù)從queue中移除并執(zhí)行task的run方法,否則的話,修改task的下次執(zhí)行時(shí)間并執(zhí)行task的run方法。
如果當(dāng)前時(shí)間小于task的執(zhí)行時(shí)間:
那么wait(executionTime - currentTime)久后,循環(huán)執(zhí)行本方法。

我們?cè)倏聪律a(chǎn)者的方法,這個(gè)方法是timer的一個(gè)方法

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

這個(gè)方法非常簡(jiǎn)單,就是把參數(shù)做了一系列的驗(yàn)證加到了queue里。那么問(wèn)題來(lái)了,每個(gè)task的觸發(fā)時(shí)間是不固定的,而消費(fèi)者函數(shù)里每次都是通過(guò)getMin函數(shù)來(lái)獲取應(yīng)該執(zhí)行的task的,那這一切是如何做到的呢?我們先看下queue的getMin函數(shù)

    TimerTask getMin() {
        return queue[1];
    }

我們看到這個(gè)函數(shù)默認(rèn)就把第一個(gè)task返回了,沒(méi)辦法,我們?cè)倏聪耡dd函數(shù)吧。

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

幾行代碼非常簡(jiǎn)單,如果需要,就執(zhí)行擴(kuò)容操作,然后把task追加到queue的尾部(task的存放從index=1開(kāi)始),重點(diǎn)是fixup這個(gè)函數(shù)。我們來(lái)看下這個(gè)函數(shù)

    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;//k = k/2;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

先說(shuō)結(jié)論,這個(gè)函數(shù)執(zhí)行完成以后,優(yōu)先級(jí)最高的task被放到了1的位置。要達(dá)到這個(gè)目的,我們直接通過(guò)遍歷這個(gè)queue也能做到,只不過(guò)這樣做的時(shí)間復(fù)雜度是O(n)。
因此,我們考慮用數(shù)組實(shí)現(xiàn)一個(gè)完全二叉樹(shù)。我們把元素按順序插入數(shù)組中后做如下規(guī)定:
假設(shè)某個(gè)元素的index=k(k>=1),那么k的左節(jié)點(diǎn)的下標(biāo)為2k,右節(jié)點(diǎn)的下標(biāo)為2k+1。
如果已知節(jié)點(diǎn)的下標(biāo)為j,那么它的頂點(diǎn)下標(biāo)為k/2。


image.png

當(dāng)我們向1插入數(shù)據(jù)的時(shí)候,此時(shí)數(shù)組只有1位置有數(shù)據(jù)。
當(dāng)我們向2插入數(shù)據(jù)的時(shí)候,如果queue[2] < queue[1],那么1和2交換位置
當(dāng)我們向3插入數(shù)據(jù)的時(shí)候,如果queue[3] < queue[1],那么1和3交換位置
當(dāng)我們向4插入數(shù)據(jù)的時(shí)候,如果queue[4] < queue[2],那么4和2交換位置,如果queue[2] < queue[1],那么1和2交換位置
當(dāng)我們向5插入數(shù)據(jù)的時(shí)候,如果queue[5] < queue[2],那么5和2交換位置,如果queue[2] < queue[1],那么1和2交換位置
...
以上就是fixUp函數(shù)做的事情,我們看到,經(jīng)過(guò)lgn(n為數(shù)組長(zhǎng)度,位置0不存任何元素)次比較,新插入的元素就在這個(gè)二叉樹(shù)中找到了自己的位置,并且頂點(diǎn)1上的元素一定是最小的(優(yōu)先級(jí)最高的)。因此,此算法的時(shí)間復(fù)雜度是O(lgn)
我們看下fixDown函數(shù)

    private void fixDown(int k) {
        int j;
        while ((j = k << 1) <= size && j > 0) {
            if (j < size &&
                queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

fixDown函數(shù)的遍歷方向和fixUp函數(shù)正好相反,是從頂點(diǎn)向下遍歷。
假設(shè)現(xiàn)在位置1的元素的優(yōu)先級(jí)被改掉了,那么先比較1的兩個(gè)葉子節(jié)點(diǎn)的大小,找出優(yōu)先級(jí)比較高的(我們假設(shè)3比較高)和頂點(diǎn)1比較,如果頂點(diǎn)的優(yōu)先級(jí)比較高,那么此次遍歷結(jié)束,否則的話,1和該3位置的元素互換互,然后再以3為頂點(diǎn),找葉子節(jié)點(diǎn)重復(fù)上述運(yùn)算,知道某個(gè)節(jié)點(diǎn)的葉子節(jié)點(diǎn)的優(yōu)先級(jí)都比該節(jié)點(diǎn)低或者遍歷到了數(shù)組的尾部結(jié)束。

通過(guò)上述介紹,我們大致了解Timer的原理,代碼雖然少,但是卻非常精悍。如果讓我實(shí)現(xiàn)這個(gè)TaskQueue我可能會(huì)選擇SparseArray吧,省內(nèi)存,系統(tǒng)自動(dòng)排序,就是效率差很多。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 寫(xiě)在前面的話 代碼中的# > 表示的是輸出結(jié)果 輸入 使用input()函數(shù) 用法 注意input函數(shù)輸出的均是字...
    FlyingLittlePG閱讀 3,208評(píng)論 0 9
  • NSThread 第一種:通過(guò)NSThread的對(duì)象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 948評(píng)論 0 3
  • 今天突然想起一些朋友,當(dāng)然我只知道那些朋友模糊的面孔,熟悉又陌生的名字,或許朋友們都已經(jīng)變樣了。 仔細(xì)想想這些...
    輕悅讀閱讀 866評(píng)論 0 1
  • 也許能成為也許不錯(cuò)的寫(xiě)手吧。會(huì)寫(xiě)一些自己喜歡的文字。愛(ài)好,希望能發(fā)展。不定期更(一些小詩(shī)、隨筆、散文、故事。) 永...
    Echo泠泠閱讀 167評(píng)論 2 2
  • 今年的向組織揩油課程結(jié)束,2016年年目標(biāo)制定也在小寶到來(lái)的喜悅和疲累中初步完成,只能說(shuō)向組織揩油活動(dòng)“超給...
    空空曉語(yǔ)閱讀 283評(píng)論 0 0

友情鏈接更多精彩內(nèi)容