Java定時(shí)器Timer的源碼分析
通過源碼分析,我們可以更深入的了解其底層原理。
對(duì)于JDK自帶的定時(shí)器,主要涉及TimerTask類、Timer類、TimerQueue類、TimerThread類,其中TimerQueue和TimerThread類與Timer類位于同一個(gè)類文件,由Timer內(nèi)部調(diào)用。
先畫上一張圖,描述一下Timer的大致模型,Timer的模型很容易理解,即任務(wù)加入到任務(wù)隊(duì)列中,由任務(wù)處理線程循環(huán)從任務(wù)隊(duì)列取出任務(wù)執(zhí)行:

一、TimerTask
TimerTask是一個(gè)任務(wù)抽象類,實(shí)現(xiàn)了Runnable接口,是可被線程執(zhí)行的。
1. 任務(wù)狀態(tài)
在TimerTask中定義了關(guān)于任務(wù)狀態(tài)的常量字段:
// 未調(diào)度狀態(tài) static final int VIRGIN = 0; // 任務(wù)已調(diào)度,但未執(zhí)行 static final int SCHEDULED = 1; // 若是一次性任務(wù)表示已執(zhí)行;可重復(fù)執(zhí)行任務(wù),該狀態(tài)無效 static final int EXECUTED = 2; // 任務(wù)被取消 static final int CANCELLED = 3;
當(dāng)一個(gè)TimerTask對(duì)象創(chuàng)建后,其初始狀態(tài)為VIRGIN;
當(dāng)調(diào)用Timer的schedule方法調(diào)度了此TimerTask對(duì)象后,其狀態(tài)變更為SCHEDULED;
如果TimerTask是一次性任務(wù),此任務(wù)執(zhí)行后,狀態(tài)將變?yōu)镋XECUTED,可重復(fù)執(zhí)行任務(wù)執(zhí)行后狀態(tài)不變;
當(dāng)中途調(diào)用了TimerTask.cancel方法,該任務(wù)的狀態(tài)將變?yōu)镃ANCELLED。
2. 任務(wù)屬性說明
TimerTask中,有如下成員變量:
// 用于加鎖控制多線程修改TimerTask內(nèi)部狀態(tài) final Object lock = new Object(); // 任務(wù)狀態(tài),初始狀態(tài)為待未調(diào)度狀態(tài) int state = VIRGIN; // 任務(wù)的下一次執(zhí)行時(shí)間點(diǎn) long nextExecutionTime; // 任務(wù)執(zhí)行的時(shí)間間隔。正數(shù)表示固定速率;負(fù)數(shù)表示固定時(shí)延;0表示只執(zhí)行一次 long period = 0;
3. 任務(wù)方法說明
TimerTask中有三個(gè)方法:
- run:實(shí)現(xiàn)了Runnable接口,創(chuàng)建TimerTask需要重寫此方法,編寫任務(wù)執(zhí)行代碼
- cancel:取消任務(wù)
- scheduledExecutionTime:計(jì)算執(zhí)行時(shí)間點(diǎn)
3.1. Cancel方法
cancel方法的實(shí)現(xiàn)代碼:
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}在cancel方法內(nèi),使用synchronized加鎖,這是因?yàn)門imer內(nèi)部的線程會(huì)對(duì)TimerTask狀態(tài)進(jìn)行修改,而調(diào)用cancel方法一般會(huì)是另外一個(gè)線程。
為了避免線程同步問題,cancel在修改狀態(tài)前進(jìn)行了加鎖操作。
調(diào)用cancel方法將會(huì)把任務(wù)狀態(tài)變更為CANCELLED狀態(tài),即任務(wù)取消狀態(tài),并返回一個(gè)布爾值,該布爾值表示此任務(wù)之前是否已是SCHEDULED 已調(diào)度狀態(tài)。
3.2. scheduledExecutionTime方法
scheduledExecutionTime方法實(shí)現(xiàn):
public long scheduledExecutionTime() {
synchronized(lock) {
return (period < 0 ? nextExecutionTime + period
: nextExecutionTime - period);
}
}該方法返回此任務(wù)的下次執(zhí)行時(shí)間點(diǎn)。
二、Timer
分析Timer源代碼,Timer在內(nèi)部持有了兩個(gè)成員變量:
private final TaskQueue queue = new TaskQueue(); private final TimerThread thread = new TimerThread(queue);
TaskQueue是任務(wù)隊(duì)列,TimerThread是任務(wù)處理線程。
1. sched方法
無論是使用schedule還是scheduleAtFixedRate方法來調(diào)度任務(wù),Timer內(nèi)部最后都是調(diào)用sched方法進(jìn)行處理。
public void schedule(TimerTask task, Date time) {
sched(task, time.getTime(), 0); // 一次性任務(wù),period為0
}
public void schedule(TimerTask task, long delay) {
...
sched(task, System.currentTimeMillis()+delay, 0); // 一次性任務(wù),period為0
}
public void schedule(TimerTask task, long delay, long period) {
...
sched(task, System.currentTimeMillis()+delay, -period); // 固定延時(shí)模式,-period
}
public void schedule(TimerTask task, Date firstTime, long period) {
...
sched(task, firstTime.getTime(), -period); // 固定延時(shí)模式,-period
}
public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
...
sched(task, System.currentTimeMillis()+delay, period); // 固定速率模式,period為正
}
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) {
...
sched(task, firstTime.getTime(), period); // 固定速率模式,period為正
}sched方法核心代碼:
private void sched(TimerTask task, long time, long period) {
...
// 加鎖,避免外部其他線程同時(shí)調(diào)用cancel,同時(shí)訪問queue產(chǎn)生線程同步問題
synchronized(queue) {
// 如果線程已終止,拋出異常
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
// 加鎖,避免多線程訪問同一個(gè)任務(wù)產(chǎn)生線程同步問題
synchronized(task.lock) {
// task的狀態(tài)必須為VIRGIN,否則認(rèn)為已經(jīng)加入調(diào)度或者已經(jīng)取消了,避免重復(fù)的調(diào)度
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
// 設(shè)置下次執(zhí)行時(shí)間點(diǎn)
task.nextExecutionTime = time;
// 設(shè)置時(shí)間間隔
task.period = period;
// 任務(wù)狀態(tài)變更為已調(diào)度
task.state = TimerTask.SCHEDULED;
}
// 將任務(wù)添加到隊(duì)列中
queue.add(task);
// 如果此任務(wù)是最近的任務(wù),喚醒線程
if (queue.getMin() == task)
queue.notify();
}
}2. cancel方法
cancel方法一般是由外部其他線程調(diào)用,而Timer內(nèi)部的線程也會(huì)對(duì)任務(wù)隊(duì)列進(jìn)行操作,因此加鎖。
public void cancel() {
synchronized(queue) {
// 修改線程的循環(huán)執(zhí)行標(biāo)志,令線程能夠終止
thread.newTasksMayBeScheduled = false;
// 清空任務(wù)隊(duì)列
queue.clear();
// 喚醒線程
queue.notify();
}
}3. purge方法
當(dāng)通過TimerTask.cancel將任務(wù)取消后,Timer的任務(wù)隊(duì)列還引用著此任務(wù),Timer只有到了要執(zhí)行時(shí)才會(huì)移除,其他時(shí)候并不會(huì)自動(dòng)將此任務(wù)移除,需要調(diào)用purge方法進(jìn)行清理。
public int purge() {
int result = 0;
synchronized(queue) {
// 遍歷隊(duì)列,將CANCELLED狀態(tài)的任務(wù)從任務(wù)隊(duì)列中移除
for (int i = queue.size(); i > 0; i--) {
if (queue.get(i).state == TimerTask.CANCELLED) {
queue.quickRemove(i);
result++;
}
}
// 如果移除任務(wù)數(shù)不為0,觸發(fā)重新排序
if (result != 0)
queue.heapify();
}
// 返回移除任務(wù)數(shù)
return result;
}三、TaskQueue
TaskQueue是Timer類文件中封裝的一個(gè)隊(duì)列數(shù)據(jù)結(jié)構(gòu),內(nèi)部默認(rèn)是一個(gè)長度128的TimerTask數(shù)組,當(dāng)任務(wù)加入時(shí),檢測(cè)到數(shù)組將滿將會(huì)自動(dòng)擴(kuò)容1倍,并對(duì)數(shù)組元素根據(jù)下次執(zhí)行時(shí)間nextExecutionTime按時(shí)間從近到遠(yuǎn)進(jìn)行排序。
void add(TimerTask task) {
// 檢測(cè)數(shù)組長度,若不夠則進(jìn)行擴(kuò)容
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2*queue.length);
// 任務(wù)入隊(duì)
queue[++size] = task;
// 排序
fixUp(size);
}fixUp方法實(shí)現(xiàn):
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
break;
TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
k = j;
}
}TaskQueue中除了fixUp方法外還有一個(gè)fixDown方法,這兩個(gè)其實(shí)就是堆排序算法,在算法專題中再進(jìn)行詳細(xì)介紹,只要記住他們的任務(wù)就是按時(shí)間從近到遠(yuǎn)進(jìn)行排序,最近的任務(wù)排在隊(duì)首即可。
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;
}
}
void heapify() {
for (int i = size/2; i >= 1; i--)
fixDown(i);
}四、TimerThread
TimerThread的核心代碼位于mainLoop方法:
private void mainLoop() {
// 死循環(huán),從隊(duì)列取任務(wù)執(zhí)行
while (true) {
try {
TimerTask task;
boolean taskFired;
// 對(duì)任務(wù)隊(duì)列加鎖
synchronized(queue) {
// 如果隊(duì)列中沒有任務(wù),則進(jìn)入等待,newTasksMayBeScheduled是線程運(yùn)行標(biāo)志位,為false時(shí)將退出循環(huán)
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
// 如果任務(wù)隊(duì)列是空的還執(zhí)行到這一步,說明newTasksMayBeScheduled為false,退出循環(huán)
if (queue.isEmpty())
break;
long currentTime, executionTime;
// 從隊(duì)列取得最近的任務(wù)
task = queue.getMin();
// 加鎖
synchronized(task.lock) {
// 如果任務(wù)狀態(tài)是已取消,則移除該任務(wù),重新循環(huán)取任務(wù)
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue;
}
// 當(dāng)前時(shí)間
currentTime = System.currentTimeMillis();
// 任務(wù)的執(zhí)行時(shí)間點(diǎn)
executionTime = task.nextExecutionTime;
// 如果執(zhí)行時(shí)間點(diǎn)早于或等于當(dāng)前時(shí)間,即過期/時(shí)間到了,則觸發(fā)任務(wù)執(zhí)行
if (taskFired = (executionTime<=currentTime)) {
// 如果任務(wù)period=0,即一次性任務(wù)
if (task.period == 0) {
// 從隊(duì)列移除一次性任務(wù)
queue.removeMin();
// 任務(wù)狀態(tài)變更為已執(zhí)行
task.state = TimerTask.EXECUTED;
} else {
// 可重復(fù)執(zhí)行任務(wù),重新進(jìn)行調(diào)度,period<0是固定時(shí)延,period>0是固定速率
queue.rescheduleMin(
task.period<0 ? currentTime - task.period // 計(jì)算下次執(zhí)行時(shí)間
: executionTime + task.period);
}
}
}
// taskFired為false即任務(wù)尚未到執(zhí)行時(shí)間點(diǎn),進(jìn)行等待,等待時(shí)間是 執(zhí)行時(shí)間點(diǎn) - 當(dāng)前時(shí)間點(diǎn)
if (!taskFired)
queue.wait(executionTime - currentTime);
}
// taskFired為true表示已觸發(fā),執(zhí)行任務(wù)
if (taskFired)
task.run();
} catch(InterruptedException e) {
}
}
}以上就是Java定時(shí)器Timer的源碼分析的詳細(xì)內(nèi)容,更多關(guān)于Java Timer的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java函數(shù)式編程之通過行為參數(shù)化傳遞代碼
行為參數(shù)化就是可以幫助你處理頻繁變更的需求的一種軟件開發(fā)模式,這篇文章將給大家詳細(xì)的介紹一下Java函數(shù)式編程之行為參數(shù)化傳遞代碼,感興趣的同學(xué)可以參考閱讀下2023-08-08
java使用Apache工具集實(shí)現(xiàn)ftp文件傳輸代碼詳解
這篇文章主要介紹了java使用Apache工具集實(shí)現(xiàn)ftp文件傳輸代碼詳解,分享了詳細(xì)連接ftp server和上傳文件,下載文件的代碼,以及結(jié)果展示,具有一定借鑒價(jià)值,需要的朋友可以參考下。2017-12-12
Spring?Security過濾器鏈體系的實(shí)例詳解
這篇文章主要介紹了Spring?Security過濾器鏈體系,通過思維導(dǎo)圖可以很好的幫助大家理解配置類的相關(guān)知識(shí),結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02
MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler
這篇文章主要為大家介紹了MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
SpringSecurity中@PermitAll與@PreAuthorize的實(shí)現(xiàn)
@PermitAll和@PreAuthorize都是處理安全性的強(qiáng)大工具,本文主要介紹了SpringSecurity中@PermitAll與@PreAuthorize的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07

