您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)定時任務(wù)ScheduledExecutorService實現(xiàn)是怎樣的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
現(xiàn)在項目中基本不會自己定義一個ScheduledExecutorService來執(zhí)行定時任務(wù)。都是用第三方框架比如xxl-job之類的,不過了解最基本的定時任務(wù)原理還是很有必要的。
我們先來看最簡單的使用,代碼如下圖:
ScheduledThreadPoolExecutor是ScheduledExecutorService的實現(xiàn),能做一些基本的定時任務(wù)功能。
比如上圖的例子,可以延遲3秒后每隔5秒執(zhí)行任務(wù)。實現(xiàn)起來比較簡單。
首先ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor并實現(xiàn)了ScheduledExecutorService,所以它擁有線程池的功能。
然后我們看它的構(gòu)造方法,它有幾個構(gòu)造方法,我們以上面示例的為例,方法中只有一行代碼如下:
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
調(diào)用了父類的構(gòu)造方法,也就是線程池的構(gòu)造方法,它自身并沒有實現(xiàn)任務(wù)事情,除了new了一個DelayedWorkQueue()給父類,這個是給線程池存放任務(wù)隊列的。
ScheduledThreadPoolExecutor提供了幾個執(zhí)行定時任務(wù)的方法,不過我們還是分析上面用到的scheduleAtFixedRate方法,查看源碼如下圖:
這個方法一共就三步:
首先初始化ScheduledFutureTask,它繼承了FutureTask,擴展了一些定時任務(wù)需要的屬性,比如下次執(zhí)行時間、每次任務(wù)執(zhí)行間隔。
然后調(diào)用decorateTask方法裝飾任務(wù),目前沒有做任務(wù)事情,我們可以自定義實現(xiàn)一些功能。
最后調(diào)用delayedExecute方法,從上圖可以看到這個方法主要流程也簡單,首先是把任務(wù)放到線程池的隊列中,然后調(diào)用ensurePrestart方法,ensurePrestart方法是線程池的方法,作用是根據(jù)線程池線程數(shù)調(diào)用addWorker方法創(chuàng)建線程,addWorker方法就不多說了,不清楚的同學(xué)可以看我前面幾篇的《三分鐘弄懂線程池執(zhí)行過程》。
提交定時任務(wù)的方法還是很簡單的,包裝一個任務(wù)(用于保存定時任務(wù)需要的信息),然后提交到線程池的隊列中。
但是有一個問題是,任務(wù)提交到線程池了,我們知道線程池能夠執(zhí)行任務(wù),但是都是執(zhí)行一次啊,不會像定時任務(wù)那樣執(zhí)行任務(wù)的!那它到底是怎么實現(xiàn)定時任務(wù)功能的呢?
從之前文章分析線程池中最終執(zhí)行的是提交的任務(wù)那個對象的run方法,所以ScheduledFutureTask對run方法有特殊的實現(xiàn),跟進(jìn)源碼發(fā)現(xiàn)run方法正常的流程就兩步:
調(diào)用ScheduledFutureTask.super.runAndReset()也就是調(diào)用了父類的FutureTask的runAndReset()方法,這個方法沒有什么在上上篇文章有講,就是調(diào)用內(nèi)部的Callable的run,保存結(jié)果;
在上一個執(zhí)行完成也就是任務(wù)完成后,調(diào)用了setNextRunTime();reExecutePeriodic(outerTask);兩步代碼,第一步是計算下次任務(wù)執(zhí)行時間,第二個是把任務(wù)放到隊列中;
run方法執(zhí)行了具體的方法,在任務(wù)執(zhí)行成功后重新計算了任務(wù)下次執(zhí)行的時間,并再次把任務(wù)加載到了隊列中,重復(fù)執(zhí)行。
但是到目前為止也只是實現(xiàn)了規(guī)定了在什么時候執(zhí)行與重復(fù)執(zhí)行,那么到底是如何實現(xiàn)在具體的時間執(zhí)行呢?
通過還是沒有發(fā)現(xiàn)如何實現(xiàn)定時執(zhí)行的,沒辦法只能繼續(xù)跟進(jìn)代碼,最后跟到線程池的runWorker方法,在獲取Worker的firstTask方法來執(zhí)行,想起剛剛在調(diào)用ensurePrestart方法中調(diào)用addWorker方法傳遞的firstTask都是null,也就是說Worker執(zhí)行的任務(wù)都是從隊列中拉取的任務(wù),他們都沒有自己的firstTask。
也就想起了在初始化方法給線程池傳遞的隊列是new DelayedWorkQueue();所以應(yīng)該基本找到問題了,我們來看隊列的take方法:
從源碼可以看到是根據(jù)隊列中第一個元素,然后利用ScheduledFutureTask的getDelay方法來判斷是否返回任務(wù)或者阻塞,getDelay是根據(jù)屬性time(前面提到的下次任務(wù)執(zhí)行時間)減去當(dāng)前時間。
那么如果第一個任務(wù)進(jìn)來判斷是10秒后執(zhí)行,第二個任務(wù)進(jìn)來判斷是5秒后執(zhí)行,那么第二個任務(wù)是不是等第一個任務(wù)執(zhí)行完才能執(zhí)行呢?
很明顯不能這么實現(xiàn),這里就要看DelayedWorkQueue的add方法了,它并沒有實現(xiàn)add方法,不過add方法調(diào)用的offer,DelayedWorkQueue實現(xiàn)了offer方法。
offer方法也簡單,就是把任務(wù)加到queue數(shù)組中,如果數(shù)組為null則直接加進(jìn)去,并喚醒take哪里的線程,如果不為null則比較數(shù)組中任務(wù)的time,越近的越在前面,如果新加進(jìn)來的任務(wù)被設(shè)置到了數(shù)組的第一個,則喚醒take那里阻塞的線程。
ScheduledExecutorService繼承線程池,也是把任務(wù)提交給線程池執(zhí)行,只不過它的任務(wù)類進(jìn)行擴展。
任務(wù)類ScheduledFutureTask繼承FutureTask并擴展了一些屬性來記錄任務(wù)下次執(zhí)行時間和每次執(zhí)行間隔。同時重寫了run方法重新計算任務(wù)下次執(zhí)行時間,并把任務(wù)放到線程池隊列中。
ScheduledExecutorService自定義了阻塞隊列DelayedWorkQueue給線程池使用,它可以根據(jù)ScheduledFutureTask的下次執(zhí)行時間來阻塞take方法,并且新進(jìn)來的ScheduledFutureTask會根據(jù)這個時間來進(jìn)行排序,最小的最前面。
總結(jié)起來就是通過線程池來執(zhí)行任務(wù),ScheduledFutureTask記錄任務(wù)定時信息,DelayedWorkQueue來排序任務(wù)定時執(zhí)行。
關(guān)于定時任務(wù)ScheduledExecutorService實現(xiàn)是怎樣的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。