30-Timer和TimerTask

Timer和TimerTask

Timer就是一個(gè)調(diào)度器蔓同,而TimerTask只是一個(gè)實(shí)現(xiàn)了run方法的類千康,而具體的TimerTask需要由你自己來實(shí)現(xiàn)据悔,例如這樣:

Timer timer = new Timer();
timer.schedule(new TimerTask() {
        public void run() {
            System.out.println("abc");
        }
}, 20000 , 1000);

這里直接實(shí)現(xiàn)一個(gè)TimerTask(當(dāng)然嗅骄,你可以實(shí)現(xiàn)多個(gè)TimerTask,多個(gè)TimerTask可以被一個(gè)Timer調(diào)度客情,后面會說到Timer的內(nèi)部調(diào)度機(jī)制),然后編寫run方法癞己,20s后開始執(zhí)行膀斋,每秒執(zhí)行一次,當(dāng)然你可以通過一個(gè)timer對象來操作多個(gè)timerTask痹雅,其實(shí)timerTask本身沒什么意義仰担,只是timer集合操作的一個(gè)對象,實(shí)現(xiàn)它就必然有對應(yīng)的run方法绩社,以被調(diào)用摔蓝,他甚至于根本不需要實(shí)現(xiàn)Runnable技掏,因?yàn)檫@樣往往混淆視聽了,為什么呢项鬼?也是本文要說的重點(diǎn)哑梳。

在說到timer的原理時(shí),我們先看看Timer里面的一些常見方法:

// 這個(gè)方法是調(diào)度一個(gè)task绘盟,經(jīng)過delay(ms)后開始進(jìn)行調(diào)度鸠真,僅僅調(diào)度一次。
public void schedule(TimerTask task, long delay);

// 在指定的時(shí)間點(diǎn)time上調(diào)度一次龄毡。
public void schedule(TimerTask task, Date time);

// 這個(gè)方法是調(diào)度一個(gè)task吠卷,在delay(ms)后開始調(diào)度,每次調(diào)度完后沦零,最少等待period(ms)后才開始調(diào)度祭隔。
public void schedule(TimerTask task, long delay, long period);

// 和上一個(gè)方法類似,唯一的區(qū)別就是傳入的第二個(gè)參數(shù)為第一次調(diào)度的時(shí)間路操。
public void schedule(TimerTask task, Date firstTime, long period);
    
/**
調(diào)度一個(gè)task疾渴,在delay(ms)后開始調(diào)度,然后每經(jīng)過period(ms)再次調(diào)度屯仗,貌似和方法:
 schedule是一樣的搞坝,其實(shí)不然,后面你會根據(jù)源碼看到魁袜,schedule在計(jì)算下一次執(zhí)行的時(shí)間的時(shí)候桩撮,
 是通過當(dāng)前時(shí)間(在任務(wù)執(zhí)行前得到) + 時(shí)間片,而scheduleAtFixedRate方法是通過當(dāng)前需要執(zhí)
 行的時(shí)間(也就是計(jì)算出現(xiàn)在應(yīng)該執(zhí)行的時(shí)間)+ 時(shí)間片峰弹,前者是運(yùn)行的實(shí)際時(shí)間店量,而后者是理論時(shí)間
 點(diǎn),例如:schedule的時(shí)間片是5s鞠呈,那么理論上會在5融师、10、15粟按、20這些時(shí)間片被調(diào)度诬滩,但是如果由于
 某些CPU征用導(dǎo)致未被調(diào)度,假如等到第8s才被第一次調(diào)度灭将,那么schedule方法計(jì)算出來的下一次時(shí)
 間應(yīng)該是第13s而不是第10s疼鸟,這樣有可能下次就越到20s后而被少調(diào)度一次或多次,而
 scheduleAtFixedRate方法就是每次理論計(jì)算出下一次需要調(diào)度的時(shí)間用以排序庙曙,若第8s被調(diào)度空镜,那
 么計(jì)算出應(yīng)該是第10s,所以它距離當(dāng)前時(shí)間是2s,那么在調(diào)度隊(duì)列排序中吴攒,會被優(yōu)先調(diào)度张抄,那么就盡
 量減少漏掉調(diào)度的情況。
**/
public void scheduleAtFixedRate(TimerTask task, long delay, long period);

// 方法同上洼怔,唯一的區(qū)別就是第一次調(diào)度時(shí)間設(shè)置為一個(gè)Date時(shí)間署惯,而不是當(dāng)前時(shí)間的一個(gè)時(shí)間片,我們在源碼中會詳細(xì)說明這些內(nèi)容镣隶。
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period);

構(gòu)造方法

首先看一下Timer的構(gòu)造方法有幾種:

構(gòu)造方法1:無參構(gòu)造方法极谊,簡單通過"Tiemer"為前綴構(gòu)造一個(gè)線程名稱:

public Timer() {
    this("Timer-" + serialNumber());
}

創(chuàng)建的線程不為主線程,則主線程結(jié)束后安岂,timer自動(dòng)結(jié)束轻猖,而無需使用cancel來完成對timer的結(jié)束。

構(gòu)造方法2:傳入了是否為后臺線程域那,后臺線程當(dāng)且僅當(dāng)進(jìn)程結(jié)束時(shí)咙边,自動(dòng)注銷掉。

public Timer(boolean isDaemon) {
    this("Timer-" + serialNumber(), isDaemon);
}

另外兩個(gè)構(gòu)造方法負(fù)責(zé)傳入名稱和將timer啟動(dòng):

public Timer(String name, boolean isDaemon) {
      thread.setName(name);
      thread.setDaemon(isDaemon);
      thread.start();
}

這里有一個(gè)thread次员,這個(gè)thread很明顯是一個(gè)線程败许,被包裝在了Timer類中,我們看下這個(gè)thread的定義是:

private TimerThread thread = new TimerThread(queue);

而定義TimerThread部分的是:

class TimerThread extends Thread 

看到這里知道了翠肘,Timer內(nèi)部包裝了一個(gè)線程檐束,用來做獨(dú)立于外部線程的調(diào)度辫秧,而TimerThread是一個(gè)default類型的束倍,默認(rèn)情況下是引用不到的,是被Timer自己所使用的盟戏。

除了上面提到的thread绪妹,還有一個(gè)很重要的屬性是:

private TaskQueue queue = new TaskQueue();

看名字就知道是一個(gè)隊(duì)列,隊(duì)列里面可以先猜猜看是什么柿究,那么大概應(yīng)該是我要調(diào)度的任務(wù)吧邮旷,先記錄下了,接下來繼續(xù)向下看:

里面還有一個(gè)屬性是:threadReaper蝇摸,它是Object類型婶肩,只是重寫了finalize方法而已,是為了垃圾回收的時(shí)候貌夕,將相應(yīng)的信息回收掉律歼,做GC的回補(bǔ),也就是當(dāng)timer線程由于某種原因死掉了啡专,而未被cancel险毁,里面的隊(duì)列中的信息需要清空掉,不過我們通常是不會考慮這個(gè)方法的,所以知道java寫這個(gè)方法是干什么的就行了畔况。

調(diào)度方法的實(shí)現(xiàn)

對于上面6個(gè)調(diào)度方法鲸鹦,我們不做一一列舉,為什么等下你就知道了:

來看下方法:

public void schedule(TimerTask task, long delay);

的源碼如下:

public void schedule(TimerTask task, long delay) {
   if (delay < 0)
       throw new IllegalArgumentException("Negative delay.");
   sched(task, System.currentTimeMillis()+delay, 0);
}

這里調(diào)用了另一個(gè)方法跷跪,將task傳入馋嗜,第一個(gè)參數(shù)傳入System.currentTimeMillis()+delay可見為第一次需要執(zhí)行的時(shí)間的時(shí)間點(diǎn)了(如果傳入Date,就是對象.getTime()即可吵瞻,所以傳入Date的幾個(gè)方法就不用多說了)嵌戈,而第三個(gè)參數(shù)傳入了0,這里可以猜下要么是時(shí)間片听皿,要么是次數(shù)啥的熟呛,不過等會就知道是什么了;另外關(guān)于方法:sched的內(nèi)容我們不著急去看他尉姨,先看下重載的方法中是如何做的庵朝。

再看看方法:

public void schedule(TimerTask task, long delay,long period);

源碼為:

public void schedule(TimerTask task, long delay, long period) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, System.currentTimeMillis()+delay, -period);
}

看來也調(diào)用了方法sched來完成調(diào)度,和上面的方法唯一的區(qū)別是增加了傳入的period又厉,而第一個(gè)傳入的是0九府,所以確定這個(gè)參數(shù)為時(shí)間片,而不是次數(shù)覆致,注意這里的period加了一個(gè)負(fù)數(shù)侄旬,也就是取反,也就是我們開始傳入1000煌妈,在調(diào)用sched的時(shí)候會變成-1000儡羔,其實(shí)最終閱讀完源碼后你會發(fā)現(xiàn)這個(gè)算是老外對于一種數(shù)字的理解,而并非有什么特殊的意義璧诵,所以閱讀源碼的時(shí)候也有這些困難所在汰蜘。

最后再看個(gè)方法是:

public void scheduleAtFixedRate(TimerTask task,long delay,long period);

源碼為:

public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
       if (delay < 0)
           throw new IllegalArgumentException("Negative delay.");
       if (period <= 0)
           throw new IllegalArgumentException("Non-positive period.");
       sched(task, System.currentTimeMillis()+delay, period);
}

唯一的區(qū)別就是period沒有取反,其實(shí)你最終閱讀完源碼之宿,上面的取反沒有什么特殊的意義族操,老外不想增加一個(gè)參數(shù)來表示scheduleAtFixedRate,而scheduleAtFixedRate和schedule的大部分邏輯代碼一致比被,因此用了參數(shù)的范圍來作為區(qū)分方法色难。

來看sched方法的實(shí)現(xiàn)體:

private void sched(TimerTask task, long time, long period) {
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");

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

queue為一個(gè)隊(duì)列,我們先不看他的數(shù)據(jù)結(jié)構(gòu)等缀,看到他在做這個(gè)操作的時(shí)候枷莉,發(fā)生了同步,所以在timer級別项滑,這個(gè)是線程安全的依沮,最后將task相關(guān)的參數(shù)賦值涯贞,主要包含nextExecutionTime(下一次執(zhí)行時(shí)間),period(時(shí)間片)危喉,state(狀態(tài))宋渔,然后將它放入queue隊(duì)列中,做一次notify操作辜限,為什么要做notify操作呢皇拣?看了后面的代碼你就知道了。

簡言之薄嫡,這里就是將task放入隊(duì)列queue的過程氧急,此時(shí),你可能對queue的結(jié)構(gòu)有些興趣毫深,那么我們先來看看queue屬性的結(jié)構(gòu)TaskQueue:

class TaskQueue {
    private TimerTask[] queue = new TimerTask[128];
    private int size = 0;
}

可見吩坝,TaskQueue的結(jié)構(gòu)很簡單,為一個(gè)數(shù)組哑蔫,加一個(gè)size钉寝,有點(diǎn)像ArrayList,是不是長度就128呢闸迷,當(dāng)然不是嵌纲,ArrayList可以擴(kuò)容,它也可以腥沽,只是會造成內(nèi)存拷貝而已逮走,所以一個(gè)Timer來講,只要內(nèi)部的task個(gè)數(shù)不超過128是不會造成擴(kuò)容的今阳;內(nèi)部提供了add(TimerTask)师溅、size()、getMin()酣栈、get(int)险胰、removeMin()、quickRemove(int)矿筝、rescheduleMin(long newTime)、isEmpty()棚贾、clear()窖维、fixUp()、fixDown()妙痹、heapify()等方法铸史。

這里面的方法大概意思是:

  • add(TimerTask t)為增加一個(gè)任務(wù);

  • size()任務(wù)隊(duì)列的長度怯伊;

  • getMin()獲取當(dāng)前排序后最近需要執(zhí)行的一個(gè)任務(wù)琳轿,下標(biāo)為1,隊(duì)列頭部0是不做任何操作的;

  • get(int i)獲取指定下標(biāo)的數(shù)據(jù)崭篡,當(dāng)然包括下標(biāo)0挪哄;

  • removeMin()為刪除當(dāng)前最近執(zhí)行的任務(wù),也就是第一個(gè)元素琉闪,通常只調(diào)度一次的任務(wù)迹炼,在執(zhí)行完后,調(diào)用此方法颠毙,就可以將TimerTask從隊(duì)列中移除斯入;

  • quickRmove(int i)刪除指定的元素,一般來說是不會調(diào)用這個(gè)方法的蛀蜜,這個(gè)方法只有在Timer發(fā)生purge的時(shí)候刻两,并且當(dāng)對應(yīng)的TimerTask調(diào)用了cancel方法的時(shí)候,才會調(diào)用這個(gè)方法滴某,也就是取消某個(gè)TimerTask闹伪,然后就會從隊(duì)列中移除(注意:如果任務(wù)正在執(zhí)行,雖然從隊(duì)列中移除了壮池,仍然會執(zhí)行完這個(gè)任務(wù))偏瓤,還有就是這個(gè)cancel方法并不是Timer的cancel方法而是TimerTask的,一個(gè)是調(diào)度器的椰憋,一個(gè)是單個(gè)任務(wù)的厅克,最后注意,這個(gè)quickRmove完成后橙依,是將隊(duì)列最后一個(gè)元素補(bǔ)充到這個(gè)位置证舟,所以此時(shí)會造成順序不一致的問題,后面會有方法進(jìn)行回補(bǔ)窗骑;

  • rescheduleMin(long newTime)是重新設(shè)置當(dāng)前執(zhí)行的任務(wù)的下一次執(zhí)行時(shí)間女责,并在隊(duì)列中將其重新排序到合適的位置,而調(diào)用的是后面說的fixDown方法创译;

對于fixUp和fixDown方法來講抵知,前者是當(dāng)新增一個(gè)task的時(shí)候,首先將元素放在隊(duì)列的尾部软族,然后向前找是否有比自己還要晚執(zhí)行的任務(wù)刷喜,如果有,就將兩個(gè)任務(wù)的順序進(jìn)行交換一下立砸。而fixDown正好相反掖疮,執(zhí)行完第一個(gè)任務(wù)后,需要加上一個(gè)時(shí)間片得到下一次執(zhí)行時(shí)間颗祝,從而需要將其順序與后面的任務(wù)進(jìn)行調(diào)整浊闪。

其次可以看下fixDown的細(xì)節(jié)為:

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

這種方式并非排序恼布,而是找到一個(gè)合適的位置來交換,因?yàn)椴⒉皇峭ㄟ^隊(duì)列逐個(gè)找的搁宾,而是每次移動(dòng)一個(gè)二進(jìn)制位折汞,例如傳入1的時(shí)候,接下來就是2猛铅、4字支、8、16這些位置奸忽,找到合適的位置放下即可堕伪,順序未必是完全有序的,它只需要看到距離調(diào)度部分的越近的是有序性越強(qiáng)的時(shí)候就可以了栗菜,這樣即可以保證一定的順序性欠雌,達(dá)到較好的性能。

最后一個(gè)方法是heapify疙筹,其實(shí)就是將隊(duì)列的后半截富俄,全部做一次fixeDown的操作,這個(gè)操作主要是為了回補(bǔ)quickRemove方法而咆,當(dāng)大量的quickRmove后霍比,順序被打亂后,此時(shí)將一半的區(qū)域做一次非常簡單的排序即可暴备。

這些方法我們不在說源碼了悠瞬,只需要知道它提供了類似于ArrayList的東西來管理,內(nèi)部有很多排序之類的處理涯捻,我們繼續(xù)回到Timer浅妆,里面還有兩個(gè)方法是:cancel()方法和purge()方法,其實(shí)就cancel方法來講障癌,一個(gè)取消操作凌外,在測試中你會發(fā)現(xiàn),如果一旦執(zhí)行了這個(gè)方法timer就會結(jié)束掉涛浙,看下源碼是什么呢:

public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();  // In case queue was already empty.
    }
}

貌似僅僅將隊(duì)列清空掉康辑,然后設(shè)置了newTasksMayBeScheduled狀態(tài)為false,最后讓隊(duì)列調(diào)用了notify操作蝗拿,但是沒有任何地方讓線程結(jié)束掉晾捏,那么就要回到我們開始說的Timer中包含的thread為:TimerThread類了,在看這個(gè)類之前哀托,再看下Timer中最后一個(gè)purge()方法,當(dāng)你對很多Task做了cancel操作后劳秋,此時(shí)通過調(diào)用purge方法實(shí)現(xiàn)對這些cancel的任務(wù)從隊(duì)列中移除仓手,上面已經(jīng)提到胖齐,此時(shí)會造成順序混亂,所以需要調(diào)用隊(duì)里的heapify方法來完成順序的重排嗽冒,源碼如下:

 public int purge() {
     int result = 0;

     synchronized(queue) {
         for (int i = queue.size(); i > 0; i--) {
             if (queue.get(i).state == TimerTask.CANCELLED) {
                 queue.quickRemove(i);
                 result++;
             }
         }

         if (result != 0)
             queue.heapify();
     }

     return result;
 }

TimerThread類

我們來看看TimerThread類:

class TimerThread extends Thread {
    boolean newTasksMayBeScheduled = true;
    private TaskQueue queue;
    
    TimerThread(TaskQueue queue) {
       this.queue = queue;
    } 
}

內(nèi)部有一個(gè)屬性是:newTasksMayBeScheduled呀伙,也就是我們開始所提及的那個(gè)在cancel的時(shí)候會被設(shè)置為false的參數(shù)。

另一個(gè)屬性也就是我們所講的任務(wù)隊(duì)列queue了添坊,這下聯(lián)通了吧剿另,不過這里的queue是通過構(gòu)造方法傳入的,很明顯是Timer傳遞給這個(gè)線程的贬蛙,我們知道它是一個(gè)線程雨女,所以執(zhí)行的中心自然是run方法了,所以看下run方法的實(shí)現(xiàn):

public void run() {
    try {
        mainLoop();
    } finally {
        // Someone killed this Thread, behave as if Timer cancelled
        synchronized(queue) {
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references
        }
    }
}

try很簡單阳准,就一個(gè)mainLoop氛堕,看名字就知道是主循環(huán)程序,finally中也就是必然執(zhí)行的程序:將參數(shù)設(shè)置為false野蝇,并將隊(duì)列清空掉讼稚。

那么最核心的就是mainLoop了,是的绕沈,看懂了mainLoop一切都懂了:

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

可以發(fā)現(xiàn)這個(gè)timer是一個(gè)死循環(huán)程序锐想,除非遇到不能捕獲的異常或break才會跳出乍狐,首先注意這段代碼:

while (queue.isEmpty() && newTasksMayBeScheduled)
    queue.wait();

循環(huán)體在循環(huán)過程中赠摇,queue為空且newTasksMayBeScheduled狀態(tài)為true,可以看到這個(gè)狀態(tài)的關(guān)鍵作用澜躺,也就是跳出循環(huán)的條件就是:要么隊(duì)列不為空蝉稳,要么是newTasksMayBeScheduled狀態(tài)設(shè)置為false。而wait就是在等待其他地方對queue發(fā)生notify操作掘鄙,從上面的代碼中可以發(fā)現(xiàn)耘戚,當(dāng)發(fā)生add、cancel以及在threadReaper調(diào)用finalize方法的時(shí)候會被調(diào)用操漠,第三個(gè)我們基本可以不考慮收津。發(fā)生add的時(shí)候也就是當(dāng)隊(duì)列還是空的時(shí)候,發(fā)生add使得隊(duì)列不為空就跳出循環(huán)浊伙,而cancel是設(shè)置了狀態(tài)撞秋,否則不會進(jìn)入這個(gè)循環(huán),那么看下面的代碼:

if (queue.isEmpty())
    break;

當(dāng)跳出上面的循環(huán)后嚣鄙,如果是設(shè)置了newTasksMayBeScheduled狀態(tài)為false跳出吻贿,也就是調(diào)用了cancel,那么queue就是空的哑子,此時(shí)就直接跳出外部的死循環(huán)舅列,所以cancel就是這樣實(shí)現(xiàn)的肌割,如果下面的任務(wù)還在跑還沒運(yùn)行到這里來,cancel是不起作用的帐要。

接下來是獲取一個(gè)當(dāng)前系統(tǒng)時(shí)間和上次預(yù)計(jì)的執(zhí)行時(shí)間把敞,如果預(yù)計(jì)執(zhí)行的時(shí)間小于當(dāng)前系統(tǒng)時(shí)間,那么就需要執(zhí)行榨惠,此時(shí)判定時(shí)間片是否為0奋早,如果為0,則調(diào)用removeMin方法將其移除赠橙,否則將task通過rescheduleMin設(shè)置最新時(shí)間并排序:

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

這里可以看到耽装,period為負(fù)數(shù)的時(shí)候,就會被認(rèn)為是按照當(dāng)前系統(tǒng)時(shí)間+一個(gè)時(shí)間片來計(jì)算下一次時(shí)間简烤,就是前面說的schedule和scheduleAtFixedRate的區(qū)別了剂邮,其實(shí)內(nèi)部是通過正負(fù)數(shù)來判定的,也許java是不想增加參數(shù)横侦,而又想增加程序的可讀性挥萌,才這樣做。

同時(shí)你可以看到period為0枉侧,就是只執(zhí)行一次引瀑,所以時(shí)間片正負(fù)0都用上了,然后再看看mainLoop接下來的部分:

if (!taskFired)// Taskhasn't yet fired; wait
    queue.wait(executionTime- currentTime);

如果任務(wù)執(zhí)行時(shí)間還未到榨馁,就等待一段時(shí)間憨栽,當(dāng)然這個(gè)等待很可能會被其他的線程操作add和cancel的時(shí)候被喚醒,因?yàn)閮?nèi)部有notify方法翼虫,所以這個(gè)時(shí)間并不是完全準(zhǔn)確屑柔。

最后:

if (taskFired) // Task fired; run it, holding no locks
    task.run();

如果任務(wù)需要執(zhí)行,那么調(diào)用它的run方法珍剑,而并非啟動(dòng)一個(gè)新的線程或從線程池中獲取一個(gè)線程來執(zhí)行掸宛,所以TimerTask的run方法并不是多線程的run方法,雖然實(shí)現(xiàn)了Runnable招拙,但是僅僅是為了表示它是可執(zhí)行的唧瘾,并不代表它必須通過線程的方式來執(zhí)行的。

回過頭來再看看:

Timer和TimerTask的簡單組合是多線程的嘛别凤?不是饰序,一個(gè)Timer內(nèi)部包裝了“一個(gè)Thread”和“一個(gè)Task”隊(duì)列,這個(gè)隊(duì)列按照一定的方式將任務(wù)排隊(duì)處理规哪,包含的線程在Timer的構(gòu)造方法調(diào)用時(shí)被啟動(dòng)求豫,這個(gè)Thread的run方法無限循環(huán)這個(gè)Task隊(duì)列,若隊(duì)列為空且沒發(fā)生cancel操作,此時(shí)會一直等待注祖,如果等待完成后猾蒂,隊(duì)列還是為空均唉,則認(rèn)為發(fā)生了cancel從而跳出死循環(huán)是晨,結(jié)束任務(wù);循環(huán)中如果發(fā)現(xiàn)任務(wù)需要執(zhí)行的時(shí)間小于系統(tǒng)時(shí)間舔箭,則需要執(zhí)行罩缴,那么根據(jù)任務(wù)的時(shí)間片重新計(jì)算下次執(zhí)行時(shí)間,若時(shí)間片為0代表只執(zhí)行一次层扶,則直接從隊(duì)列移除即可箫章。

但是是否能實(shí)現(xiàn)多線程呢?可以镜会,任何東西是否是多線程完全看個(gè)人意愿檬寂,多個(gè)Timer自然就是多線程的,每個(gè)Timer都有自己的線程處理邏輯戳表,當(dāng)然Timer從這里來看并不是很適合很多任務(wù)在短時(shí)間內(nèi)的快速調(diào)度桶至,至少不是很適合同一個(gè)timer上掛很多任務(wù),在多線程的領(lǐng)域中我們更多是使用多線程中的:

Executors.newScheduledThreadPool

來完成對調(diào)度隊(duì)列中的線程池的處理匾旭,內(nèi)部通過new ScheduledThreadPoolExecutor來創(chuàng)建線程池的镣屹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市价涝,隨后出現(xiàn)的幾起案子女蜈,更是在濱河造成了極大的恐慌,老刑警劉巖色瘩,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伪窖,死亡現(xiàn)場離奇詭異,居然都是意外死亡居兆,警方通過查閱死者的電腦和手機(jī)覆山,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來史辙,“玉大人汹买,你說我怎么就攤上這事×木螅” “怎么了晦毙?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長耙蔑。 經(jīng)常有香客問我见妒,道長,這世上最難降的妖魔是什么甸陌? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任须揣,我火速辦了婚禮盐股,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘耻卡。我一直安慰自己疯汁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布卵酪。 她就那樣靜靜地躺著幌蚊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪溃卡。 梳的紋絲不亂的頭發(fā)上溢豆,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機(jī)與錄音瘸羡,去河邊找鬼漩仙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛犹赖,可吹牛的內(nèi)容都是我干的队他。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼冷尉,長吁一口氣:“原來是場噩夢啊……” “哼漱挎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起雀哨,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤磕谅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后雾棺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膊夹,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年捌浩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了放刨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尸饺,死狀恐怖进统,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浪听,我是刑警寧澤螟碎,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站迹栓,受9級特大地震影響掉分,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一酥郭、第九天 我趴在偏房一處隱蔽的房頂上張望华坦。 院中可真熱鬧,春花似錦不从、人聲如沸惜姐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽载弄。三九已至,卻和暖如春撵颊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惫叛。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工倡勇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嘉涌。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓妻熊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親仑最。 傳聞我的和親對象是個(gè)殘疾皇子扔役,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

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

  • Timer 定時(shí)器相信都不會陌生,之所以拿它來做源碼分析警医,是發(fā)現(xiàn)整個(gè)控制流程可以體現(xiàn)很多有意思的東西亿胸。 在業(yè)務(wù)開發(fā)...
    石先閱讀 6,367評論 2 13
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴预皇。侈玄。 不如我直接引用一個(gè)最簡單的問題,以這個(gè)作為切入點(diǎn)好了 在ma...
    Mr_Baymax閱讀 2,766評論 1 17
  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的吟温,后來想想還是整...
    coder_pig閱讀 1,655評論 2 17
  • 上一篇唐寶告訴大家在作曲過程中如何確定音樂風(fēng)格的調(diào)和樂章鲁豪。一般的情歌其實(shí)章節(jié)很多潘悼,我們主要寫一些小情歌,給自己的詞...
    唐詩遠(yuǎn)閱讀 5,058評論 7 47
  • 文/豌豆白 01 這是她在校園里晃悠的第四個(gè)夜晚爬橡。 她總是對四情有獨(dú)鐘治唤。 她一直記得一個(gè)同學(xué)告訴過她,四代表著轉(zhuǎn)機(jī)...
    豌豆白閱讀 368評論 2 4