線程并發(fā)->07Timer

一、參考文章:

二掰吕、Timer問題:

  • 1果覆、如何開啟任務(wù);
  • 2、如何停止正在運(yùn)行的任務(wù);
  • 3殖熟、線程間通信;
  • 4局待、Timer有什么缺陷;

三、demo:

private Timer mTimer;

public void timerStart() {
    TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
            LogUtils.log(getClass(), "timerTask");
        }
    };
    mTimer = new Timer();
    mTimer.schedule(timerTask, 0, 500);
}

public void timerCancel() {
    mTimer.cancel();
}

四吗讶、Timer任務(wù)開啟:

4.1 Timer構(gòu)造函數(shù):
public class Timer {

    private final TaskQueue queue = new TaskQueue();
    
    private final TimerThread thread = new TimerThread(queue);
    /**
     * 1. Timer初始化時, 創(chuàng)建一個final類型的TaskQueue和TimerThread;  
     * 2. TimerThread持有TaskQueue的引用, 然后直接調(diào)用thread.start方法, 目前猜測可能
     *    用到了生產(chǎn)者-消費(fèi)者模式, 通過TaskQueue.add喚醒當(dāng)前TimerThread;
     */
    public Timer() {
        this("Timer-" + serialNumber());
    }

    public Timer(String name) {
        thread.setName(name);
        /**
         * 開啟任務(wù)線程, start方法會觸發(fā)其內(nèi)部的run方法執(zhí)行, 這里也就是說一旦初始化
         * Timer, 就會開啟任務(wù)線程; 
         */
        thread.start();    模塊<4.2>
    }
}

class TaskQueue {
    void rescheduleMin(long newTime) {
        queue[1].nextExecutionTime = newTime;
        fixDown(1);
    }
}
4.2 TimerThread.run:
class TimerThread extends Thread {
    public void run() {
        try {
            mainLoop();
        } finally {
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear();  
            }
        }
    }

    private void mainLoop() {
        /**
         * 采用while-true的方式, 后續(xù)分析要重點關(guān)注如何退出while-true;<TODO>
         */
        while (true) {
            TimerTask task;
            boolean taskFired;
            /**
             * 這里的synchronized是為了<//2--->>的queue.wait使用;
             */
            synchronized(queue) {
                /**
                 * 1. newTasksMayBeScheduled表示當(dāng)前Timer是否可用, 默認(rèn)true表示可用;
                 * 2. 如果queue為空, 且Timer可用, 則進(jìn)入<//2--->>掛起當(dāng)前線程, 并且釋放鎖;
                 */
//1--->
                while (queue.isEmpty() && newTasksMayBeScheduled)
//2--->             /**
//                   * 如果當(dāng)前沒有要執(zhí)行的任務(wù), 則線程在這里被掛起;
//                   */
                    queue.wait();
                /**
                 * 執(zhí)行if內(nèi)的break需要以下兩種條件:
                 *  1. queue.isEmpty() == true;
                 *  2. newTasksMayBeSchedule == false;
                 */
//3--->
                if (queue.isEmpty())
                    break;
                long currentTime, executionTime;
                /**
                 * 執(zhí)行到這里說明當(dāng)前TaskQueue不為empty, 從TaskQueue中取出TimerTask;
                 */
                task = queue.getMin();
                synchronized(task.lock) {
                    // 如果當(dāng)前任務(wù)已經(jīng)被取消, 則從TaskQueue中移除當(dāng)前TimerTask;
                    if (task.state == TimerTask.CANCELLED) {
                        queue.removeMin();
                        continue;  
                    }
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;
                    /**
                     * 結(jié)合模塊<五>可知, executionTime為初始化TimerTask時指定的執(zhí)行時間:
                     * 1. 如果executionTime > currentTime即TimerTask需要延時處理, taskFired = false,
                     *    跳轉(zhuǎn)至<//4--->>進(jìn)入阻塞狀態(tài);        
                     * 2. 如果executionTime ≤ currentTime即TimerTask不需要延時處理, taskFired = true,
                     *    跳轉(zhuǎn)至<//5--->>執(zhí)行run方法;
                     */
                    if (taskFired = (executionTime<=currentTime)) {
                        /**
                         * period相當(dāng)于定時器的作用, 每隔period時間執(zhí)行一次run方法; 分兩種情況:
                         * 1. period > 0, 每隔period時間通過rescheduleMin刷新任務(wù)下一次的執(zhí)行時間點;
                         * 2. period = 0, task執(zhí)行完之后, 從queue中移除, 即TimerTask.run只會執(zhí)行一次; 
                         */
                        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);
                        }
                    }
                }
//4--->
                if (!taskFired)
                    /**
                     * 當(dāng)前線程被掛起executionTime - currentTime時間之后被喚醒繼續(xù)向下執(zhí)行;
                     */
                    queue.wait(executionTime - currentTime);
            }
//5--->   
            if (taskFired)  
                /**
                 * 1. 初始化Timer時, 會初始化一個final類型的TimerThread, 一個Timer有且僅有一個
                 *    TimerThread, 所以多個任務(wù)在這里進(jìn)行串行執(zhí)行, 這里就會有一個弊端, 模塊<7>
                 *    使用demo進(jìn)行說明;
                 * 2. 當(dāng)前run方法執(zhí)行完成以后, 再次執(zhí)行while(true)嘗試用queue中取出task執(zhí)行;
                 */
                task.run();
        }
    }
}

五燎猛、Timer任務(wù)執(zhí)行:

public class Timer {
    public void schedule(TimerTask task, long delay, long period) {
        sched(task, System.currentTimeMillis()+delay, -period);
    }

    private void sched(TimerTask task, long time, long period) {
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;

        synchronized(queue) {
            /**
             * thread.newTasksMayBeScheduled默認(rèn)為true, 如果thread.newTasksMayBeScheduled
             * 為false(即模塊<6>調(diào)用Timer.cancel方法), 再次調(diào)用Timer.schedule方法, 會拋出此異常;
             */
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");
            /**
             * 對TimerTask進(jìn)行初始化操作, state默認(rèn)為SCHEDULED;
             */
            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();
        }
    }

    class TaskQueue {

        void add(TimerTask task) {
            if (size + 1 == queue.length)
                queue = Arrays.copyOf(queue, 2*queue.length);
            queue[++size] = task;
            fixUp(size);
        }
    
        TimerTask getMin() {
            return queue[1];
        }
    }
}

六、任務(wù)取消:

public void cancel() {
    /**
     * 這里用的鎖對象與模塊<4.2>run和模塊<五>是同一個鎖對象, 所以取消當(dāng)前正在執(zhí)行的任務(wù)一定是在
     * 當(dāng)前任務(wù)執(zhí)行完成之后, 或者在schedule完成之后才會執(zhí)行;
     */
    synchronized(queue) {
        /**
         * 1. 調(diào)用cancel之后, newTasksMayBeScheduled = false, 響應(yīng)模塊<五>的schedule方法;
         * 2. 調(diào)用queue.clear()響應(yīng)模塊<4.2>的<//3--->>處由于queue.isEmpty()且
         *    newTasksMayBeScheduled = false觸發(fā)break的執(zhí)行導(dǎo)致跳出當(dāng)前while(true);
         * 3. queue.notify是為了喚醒模塊<4.2>當(dāng)queue為空時, 通過queue.wait方式被掛起的線程, 
         *    此時線程被喚醒之后, 再次執(zhí)行while(true)從而退出while(true)循環(huán);
         */
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();  // In case queue was already empty.
    }
}

七照皆、反應(yīng)模塊<4.2>_<//5--->>的一個弊端代碼:

public class TimerTest {

    private static long start;

    public static void timerTest() {
        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                LogUtils.log(getClass(), "task1 invoked, " + (System.currentTimeMillis() - start));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                LogUtils.log(getClass(), "task2 invoked, " + (System.currentTimeMillis() - start));
            }
        };
        Timer timer = new Timer();
        start = System.currentTimeMillis();
        timer.schedule(task1, 1000);
        timer.schedule(task2, 3000);
    }
}
  • 這段代碼取自張鴻洋的文章;
    打印結(jié)果如下:
05-06 19:58:06.286 4582-4648/com.test V/AndroidTest: ->task1 invoked, 1000
05-06 19:58:09.286 4582-4648/com.test V/AndroidTest: ->task2 invoked, 4001

為何會是這樣一種結(jié)果?
模塊<4.2>的<//5--->>部分說明了Timer內(nèi)部只有一個線程, 所以通過schedule方式添加的TimerTask是以串行的方式進(jìn)行, 這就導(dǎo)致task2必須要在task1執(zhí)行完成以后才能執(zhí)行, 而task1執(zhí)行耗時包括delay(1000) + sleep(3000) = time(4000)

因此如果使用Timer連續(xù)執(zhí)行多個task, 后面每個task執(zhí)行的時間很可能會不準(zhǔn)確;

  • 弊端的原因是因為每次提交任務(wù)之后, 任務(wù)是以串行的方式在TimerThread內(nèi)部執(zhí)行,
    而如果后繼Task的執(zhí)行啟動時間小于前繼Task的執(zhí)行(啟動時間+執(zhí)行時間), 則后繼
    任務(wù)的執(zhí)行將會被推遲;

八重绷、Timer如何保證多任務(wù)串行的方式執(zhí)行:

8.1 Timer.schedule與TimerThread.run:
class TimerThread extends Thread {

    private void mainLoop() {
        while (true) {
            synchronized(queue) {
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
            }
        }
        if (taskFired) 
            task.run();
    }
} 
public class Timer{

    public void schedule(TimerTask task, long delay, long period) {
        sched(task, System.currentTimeMillis()+delay, -period);
    }

    private void sched(TimerTask task, long time, long period) {
        synchronized(queue) {
            queue.add(task);
            if (queue.getMin() == task)
                queue.notify();
        }
    }
}
  • 1、上面只列了代碼片段, queue為final類型, mainLoop(Thread_1)和schedule(Thread_2)可以認(rèn)為是消費(fèi)者--生產(chǎn)者模式;
  • 2膜毁、mainLoop中如果queue.isEmpty == true, 則釋放鎖并掛起當(dāng)前線程, 然后sched所處線程嘗試獲取鎖, 添加task然后notify喚醒線程Thread_1;
  • 3昭卓、如果queue.isEmpty == false, 則Thread_1持有鎖, Thread_2 執(zhí)行sched時被掛起, 直到 Thread_1 執(zhí)行完task.run之后才會釋放鎖, 然后Thread_2嘗試獲取鎖像queue中添加Task;
  • 4、所以Task能被成功添加到queue中一定是滿足queue.isEmpty == true 或者 Thread_1 執(zhí)行task.run結(jié)束;
  • 5瘟滨、所以Task(N)的實際啟動時間 == Max(Task(N)啟動時間, Task(N-1)啟動時間 + Task(N - 1)執(zhí)行時間);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末候醒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子杂瘸,更是在濱河造成了極大的恐慌倒淫,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件败玉,死亡現(xiàn)場離奇詭異敌土,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)运翼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門返干,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人血淌,你說我怎么就攤上這事矩欠。” “怎么了悠夯?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵癌淮,是天一觀的道長。 經(jīng)常有香客問我疗疟,道長该默,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任策彤,我火速辦了婚禮栓袖,結(jié)果婚禮上匣摘,老公的妹妹穿的比我還像新娘。我一直安慰自己裹刮,他們只是感情好音榜,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著捧弃,像睡著了一般赠叼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上违霞,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天嘴办,我揣著相機(jī)與錄音,去河邊找鬼买鸽。 笑死涧郊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的眼五。 我是一名探鬼主播妆艘,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼看幼!你這毒婦竟也來了批旺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诵姜,失蹤者是張志新(化名)和其女友劉穎汽煮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棚唆,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逗物,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瑟俭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡契邀,死狀恐怖摆寄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坯门,我是刑警寧澤微饥,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站古戴,受9級特大地震影響欠橘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜现恼,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一肃续、第九天 我趴在偏房一處隱蔽的房頂上張望黍檩。 院中可真熱鬧,春花似錦始锚、人聲如沸刽酱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棵里。三九已至,卻和暖如春姐呐,著一層夾襖步出監(jiān)牢的瞬間殿怜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工曙砂, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留头谜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓麦轰,卻偏偏與公主長得像乔夯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子款侵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Timer 定時器相信都不會陌生末荐,之所以拿它來做源碼分析,是發(fā)現(xiàn)整個控制流程可以體現(xiàn)很多有意思的東西新锈。 在業(yè)務(wù)開發(fā)...
    石先閱讀 6,345評論 2 13
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,152評論 25 707
  • 先運(yùn)行一段測試代碼 代碼中生成一個間隔5s甲脏,tolerance為0.5s的NSTimer,加入主線程的RunLoo...
    jinqiushi閱讀 5,309評論 11 47
  • 有一次妹笆,我跟媽媽去菜場買小蔥块请,我忽然有了一個有趣的想法--種蔥。 我先準(zhǔn)備了花盆拳缠、泥土和水墩新,接著倒入一半泥土放進(jìn)花...
    3b7d71778f07閱讀 475評論 1 1
  • 今天真正體驗到,直面面對一切窟坐,所有的問題似乎都變簡單了不少海渊,恐慌與不安也會相應(yīng)的減少,困難也并非想象中的難以克服哲鸳。...
    大絲瓜君閱讀 277評論 0 0