理解android中的Timer

前言

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

正文

Timer內部有2個比較重要的成員變量

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

一個是任務隊列,一個是執(zhí)行任務的線程命浴。我們線看下這個 TimerThread,它繼承自Thread贱除,并聲明了一個mainLoop方法生闲,當點用 thread.start后,它就會執(zhí)行這個方法

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

我們看這個方法月幌,典型的就是一個生產(chǎn)者消費者的模型碍讯。
如果queue里沒有數(shù)據(jù),那么它就通過點用wait方法釋放鎖扯躺,等待被生產(chǎn)者喚醒捉兴。當方法再次被喚醒的時候,代碼先去檢查queue是不是空的录语,是空的就繼續(xù)wait倍啥,否則的話,通過queue的getMin函數(shù)獲取一個任務task澎埠,
如果當前時間大于等于task的執(zhí)行時間:
說明該任務該執(zhí)行了虽缕,如果當前任務不是重復任務,那么直接把任務從queue中移除并執(zhí)行task的run方法蒲稳,否則的話氮趋,修改task的下次執(zhí)行時間并執(zhí)行task的run方法。
如果當前時間小于task的執(zhí)行時間:
那么wait(executionTime - currentTime)久后江耀,循環(huán)執(zhí)行本方法凭峡。

我們再看下生產(chǎn)者的方法,這個方法是timer的一個方法

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

這個方法非常簡單决记,就是把參數(shù)做了一系列的驗證加到了queue里摧冀。那么問題來了,每個task的觸發(fā)時間是不固定的,而消費者函數(shù)里每次都是通過getMin函數(shù)來獲取應該執(zhí)行的task的索昂,那這一切是如何做到的呢建车?我們先看下queue的getMin函數(shù)

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

我們看到這個函數(shù)默認就把第一個task返回了,沒辦法椒惨,我們再看下add函數(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);
    }

幾行代碼非常簡單,如果需要康谆,就執(zhí)行擴容操作领斥,然后把task追加到queue的尾部(task的存放從index=1開始),重點是fixup這個函數(shù)沃暗。我們來看下這個函數(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;
        }
    }

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


image.png

當我們向1插入數(shù)據(jù)的時候,此時數(shù)組只有1位置有數(shù)據(jù)陌僵。
當我們向2插入數(shù)據(jù)的時候,如果queue[2] < queue[1]创坞,那么1和2交換位置
當我們向3插入數(shù)據(jù)的時候碗短,如果queue[3] < queue[1],那么1和3交換位置
當我們向4插入數(shù)據(jù)的時候题涨,如果queue[4] < queue[2]偎谁,那么4和2交換位置,如果queue[2] < queue[1]纲堵,那么1和2交換位置
當我們向5插入數(shù)據(jù)的時候巡雨,如果queue[5] < queue[2],那么5和2交換位置席函,如果queue[2] < queue[1]铐望,那么1和2交換位置
...
以上就是fixUp函數(shù)做的事情,我們看到,經(jīng)過lgn(n為數(shù)組長度正蛙,位置0不存任何元素)次比較督弓,新插入的元素就在這個二叉樹中找到了自己的位置,并且頂點1上的元素一定是最小的(優(yōu)先級最高的)乒验。因此愚隧,此算法的時間復雜度是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ù)正好相反,是從頂點向下遍歷锻全。
假設現(xiàn)在位置1的元素的優(yōu)先級被改掉了狂塘,那么先比較1的兩個葉子節(jié)點的大小,找出優(yōu)先級比較高的(我們假設3比較高)和頂點1比較鳄厌,如果頂點的優(yōu)先級比較高荞胡,那么此次遍歷結束,否則的話部翘,1和該3位置的元素互換互硝训,然后再以3為頂點,找葉子節(jié)點重復上述運算新思,知道某個節(jié)點的葉子節(jié)點的優(yōu)先級都比該節(jié)點低或者遍歷到了數(shù)組的尾部結束窖梁。

通過上述介紹,我們大致了解Timer的原理夹囚,代碼雖然少纵刘,但是卻非常精悍。如果讓我實現(xiàn)這個TaskQueue我可能會選擇SparseArray吧荸哟,省內存假哎,系統(tǒng)自動排序,就是效率差很多鞍历。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末舵抹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子劣砍,更是在濱河造成了極大的恐慌惧蛹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刑枝,死亡現(xiàn)場離奇詭異香嗓,居然都是意外死亡,警方通過查閱死者的電腦和手機装畅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門靠娱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掠兄,你說我怎么就攤上這事像云⌒咳福” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵苫费,是天一觀的道長汤锨。 經(jīng)常有香客問我,道長百框,這世上最難降的妖魔是什么闲礼? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮铐维,結果婚禮上柬泽,老公的妹妹穿的比我還像新娘。我一直安慰自己嫁蛇,他們只是感情好锨并,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著睬棚,像睡著了一般第煮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抑党,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天包警,我揣著相機與錄音,去河邊找鬼底靠。 笑死害晦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的暑中。 我是一名探鬼主播壹瘟,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鳄逾!你這毒婦竟也來了稻轨?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雕凹,失蹤者是張志新(化名)和其女友劉穎殴俱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體请琳,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡粱挡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年赠幕,在試婚紗的時候發(fā)現(xiàn)自己被綠了俄精。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡榕堰,死狀恐怖竖慧,靈堂內的尸體忽然破棺而出嫌套,到底是詐尸還是另有隱情,我是刑警寧澤圾旨,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布踱讨,位于F島的核電站,受9級特大地震影響砍的,放射性物質發(fā)生泄漏痹筛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一廓鞠、第九天 我趴在偏房一處隱蔽的房頂上張望帚稠。 院中可真熱鬧,春花似錦床佳、人聲如沸滋早。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杆麸。三九已至,卻和暖如春浪感,著一層夾襖步出監(jiān)牢的瞬間昔头,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工篮撑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留减细,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓赢笨,卻偏偏與公主長得像未蝌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茧妒,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內容

  • 寫在前面的話 代碼中的# > 表示的是輸出結果 輸入 使用input()函數(shù) 用法 注意input函數(shù)輸出的均是字...
    FlyingLittlePG閱讀 2,758評論 0 8
  • NSThread 第一種:通過NSThread的對象方法 NSThread *thread = [[NSThrea...
    攻城獅GG閱讀 799評論 0 3
  • 今天突然想起一些朋友桐筏,當然我只知道那些朋友模糊的面孔纸型,熟悉又陌生的名字,或許朋友們都已經(jīng)變樣了梅忌。 仔細想想這些...
    輕悅讀閱讀 627評論 0 1
  • 也許能成為也許不錯的寫手吧狰腌。會寫一些自己喜歡的文字。愛好牧氮,希望能發(fā)展琼腔。不定期更(一些小詩、隨筆踱葛、散文丹莲、故事光坝。) 永...
    Echo泠泠閱讀 149評論 2 2
  • 今年的向組織揩油課程結束,2016年年目標制定也在小寶到來的喜悅和疲累中初步完成甥材,只能說向組織揩油活動“超給...
    空空曉語閱讀 225評論 0 0