做者:點先生 時間:2019.3.27java
Q:定時、延時任務有幾種方式能夠實現?
A:Handler、Timer、ScheduledThreadPool、AlarmManagerapi
Handler機制你們應該都爛熟於心了,今天我來說講Timer這個不常被問到的定時器。 改日再說線程池,預計是週日。數組
Timer機制包含了四個主要核心類:Timer,TaskQueue,TimerThread,TimerTask。我們一個個來了解。安全
Timer類加載時建立新的任務隊列,新的定時器線程。並將兩個綁定起來。oop
public class Timer {
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
}
複製代碼
初始化Timerui
反正就是給thread設置名字,或者設置是不是守護線程,最後開啓線程;這個thread,就是TimerThread。this
調用這四個方法能夠執行定時任務、延時任務、週期執行任務。spa
這兩個方法與上面最後兩個方法很相似,不一樣的地方在於sched()的最後一個參數,傳入當前值或是相反數值,這裏的具體影響後面會介紹到。sched()的核心代碼爲:線程
private void sched(TimerTask task, long time, long period) {
//其餘邏輯
synchronized(queue) {
//其餘邏輯
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();
}
}
複製代碼
主要就是將初始化後的task進行賦值,而後加入隊列。 至此Timer裏面就還有兩個方法沒說到。code
public void cancel(){ 清空隊列,通知隊列 }
public int purge(){ 將隊列中狀態爲「CANCELLED」的任務移除,並重排序隊列。 }
複製代碼
任務隊列實際上就是一個TimerTask的大小爲128的數組。size表示隊列中的任務數。 其餘的就是一些操做此數組的方法
int size() { 獲取當前任務數 }
void add(TimerTask task){ 添加任務到數組,並 fixUp(size),第一個元素的位置爲1,非0。 }
TimerTask getMin(){ 獲得最近的一個任務 }
TimerTask get(int i){ 獲得i元素 }
void removeMin(){ 移除最近的一個任務,並 fixDown(1) }
void quickRemove(int i){ 快速移速某個任務,不重排序 }
void rescheduleMin(long newTime){ 從新設置最近任務的執行時間,並 fixDown(1) }
boolean isEmpty(){ 判斷隊列是否爲空 }
void clear(){ 清空隊列 }
void fixUp(int k){ 排序方法1 }
void fixDown(int k){ 排序方法2 }
void heapify(){ 排序方法3 }
複製代碼
三種排序方式再也不此深探究。在此留下一個疑問,爲什麼第一個任務添加進來給的位置是1,非0;
class TimerThread extends Thread {
boolean newTasksMayBeScheduled = true;
private TaskQueue queue TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try { mainLoop();
} finally {
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear();
}
}
}
複製代碼
最後來看看mainLoop()中的核心代碼:
private void mainLoop() {
while (true) {
try {
synchronized(queue) {
//其餘邏輯
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
//其餘邏輯
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) {
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else {
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
if (!taskFired)
queue.wait(executionTime - currentTime);
}
if (taskFired)
task.run();
} catch(InterruptedException e) {
}
}
}
複製代碼
線程運行起來以後,就一直在取最近的消息對比當前時間,執行時間到了,就看是不是一次性任務。若是是一次性任務,就更改任務狀態。若是是週期任務,就把給任務設置新的執行時間再入隊列。若是一開始執行時間就沒到,就wait當前隊列。最後根據執行時間是否到達,執行取出來的最近任務。
tips:週期任務重置時間時,有兩種時間,當period<0時currentTime - task.period ,當period>0時executionTime + task.period。根據Timer中sched()和scheduleAtFixedRate()的區別能推斷出,前者代碼表示,當前任務執行完以後,再進入period時間。後這代碼表示,當前任務執行開始的時,就進入period時間。
TimerTask是個抽象類,實現了Runnable接口。內部擁有四個屬性,三個方法。
public abstract class TimerTask implements Runnable {
final Object lock = new Object(); //對象鎖,用於維護線程安全;
int state = VIRGIN;//狀態值
long nextExecutionTime;//下一次執行的時間
long period = 0;//週期時間
static final int VIRGIN = 0;
static final int SCHEDULED = 1;
static final int EXECUTED = 2;
static final int CANCELLED = 3;
}
複製代碼
VIRGIN :初始化默認值,表達此任務還沒被加入執行隊列。
SCHEDULED :任務被安排準備執行,已加入執行隊列
EXECUTED : 任務正在執行或者已經執行,還沒被取消。
CANCELLED :任務已經被取消
public abstract void run();
public boolean cancel() {
synchronized(lock) {
boolean result = (state == SCHEDULED);
state = CANCELLED;
return result;
}
}
public long scheduledExecutionTime() {
synchronized(lock) {
return (period < 0 ? nextExecutionTime + period
: nextExecutionTime - period);
}
}
複製代碼
繼承TimerTask或者匿名內部類建立均可以實現執行定時任務,將要執行的動做寫在run()裏面便可。 cancel()返回當前任務狀態值是不是SCHEDULED,再將其狀態值改爲CANCELLED。 scheduledExecutionTime()返回的是下一次執行的時間。
優缺點 | Handler | Timer |
---|---|---|
執行同一個非週期任務 | 只須要再發一次消息 | 須要建立新的TimerTask |
通訊 | 線程間通訊靈活 | TimerTask執行在子線程中 |
可靠性 | 週期執行任務比較可靠 | 週期執行任務不可靠(下面解釋) |
內存泄漏 | 容易泄漏 | 容易泄漏 |
內存消耗 | 小 | 相對較大 |
靈活性 | 依賴looper,不靈活 | Timer不依賴其餘類 |
Timer執行的週期任務容易被自身干擾。(當耗時任務在sched()中執行時候,會大大延遲下一次任務的執行;當耗時任務須要操做同一個對象在scheduleAtFixedRate()中執行的時候,拿不到任務對象,等待上一次的任務釋放鎖。)
Handler適合大多數場景,且好處理。 Timer只適合執行耗時比較少的重複任務。 難怪Timer相關文章熱度這麼低,看完源碼才知道,是個小辣雞。這兩天時間算是浪費了。
最後但願你們多多關注咱們的博客團隊:天星技術博客https://juejin.im/user/5afa539751882542aa42e5c5