一、參考文章:
二掰吕、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í)行時間);