Java?Timer單線程下的定時(shí)任務(wù)舉例詳解
引言
Timer 是 Java 標(biāo)準(zhǔn)庫(java.util 包)中用于 定時(shí)調(diào)度任務(wù) 的工具類,它允許程序在指定時(shí)間點(diǎn)執(zhí)行任務(wù),或按固定周期重復(fù)執(zhí)行任務(wù)。簡單來說,Timer 就像一個(gè) “定時(shí)器”,可以幫你規(guī)劃代碼在未來的某個(gè)時(shí)間運(yùn)行,或周期性地運(yùn)行。
在學(xué)習(xí) Timer 的用法和底層原理前,最需要了解的核心數(shù)據(jù)結(jié)構(gòu)是 優(yōu)先級隊(duì)列,具體來說是 Timer 內(nèi)部使用的 基于小頂堆實(shí)現(xiàn)的優(yōu)先級隊(duì)列。
什么是小頂堆
小頂堆是一顆完全二叉樹,它的根節(jié)點(diǎn)是最小的元素,然后每個(gè)子節(jié)點(diǎn)都必須比它的父節(jié)點(diǎn)存的值要大。

在 Timer 中,小頂堆(TaskQueue)的堆頂元素是 “最早需要執(zhí)行的定時(shí)任務(wù)(TimerTask)”。
具體來說,TaskQueue 會按照任務(wù)的 nextExecutionTime(下一次執(zhí)行時(shí)間)對所有 TimerTask 進(jìn)行排序,而小頂堆的堆頂元素就是 nextExecutionTime 最小的任務(wù),也就是當(dāng)前所有任務(wù)中 “最早到點(diǎn)需要執(zhí)行” 的那個(gè)任務(wù)。
Timer 類的核心結(jié)構(gòu)
public class Timer {
// 任務(wù)隊(duì)列(基于小頂堆實(shí)現(xiàn))
private final TaskQueue queue = new TaskQueue();
// 調(diào)度線程(負(fù)責(zé)執(zhí)行任務(wù))
private final TimerThread thread = new TimerThread(queue);
// 構(gòu)造方法:啟動調(diào)度線程
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(String name) {
thread.setName(name);
thread.start(); // 啟動TimerThread
}
// 提交任務(wù)的核心方法(以schedule為例)
public void schedule(TimerTask task, Date time) {
// 計(jì)算延遲時(shí)間(相對于當(dāng)前時(shí)間)
long delay = time.getTime() - System.currentTimeMillis();
if (delay < 0) delay = 0;
// 調(diào)用內(nèi)部方法添加任務(wù)
sched(task, System.currentTimeMillis() + delay, 0);
}
// 實(shí)際添加任務(wù)到隊(duì)列的方法
private void sched(TimerTask task, long time, long period) {
if (time < 0) throw new IllegalArgumentException("Illegal execution time.");
// 同步鎖:保證任務(wù)隊(duì)列操作的線程安全
synchronized(queue) {
if (thread.isInterrupted())
throw new IllegalStateException("Timer already cancelled.");
// 設(shè)置任務(wù)的執(zhí)行時(shí)間和周期
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
// 將任務(wù)添加到隊(duì)列(小頂堆)
queue.add(task);
// 如果添加的是堆頂任務(wù),喚醒調(diào)度線程(可能在等待中)
if (queue.getMin() == task)
queue.notify();
}
}
}任務(wù)隊(duì)列(TaskQueue):基于小頂堆的實(shí)現(xiàn)
在Java中,不管是二叉樹,還是堆,它們都只是邏輯結(jié)構(gòu),并不是真正意義上的存儲結(jié)構(gòu),具體的存儲結(jié)構(gòu)都是基于數(shù)組或者鏈表來實(shí)現(xiàn)的。
class TaskQueue {
// 用數(shù)組存儲堆元素(小頂堆)
private TimerTask[] queue = new TimerTask[128];
private int size = 0; // 當(dāng)前任務(wù)數(shù)量
// 添加任務(wù)(入隊(duì))
void add(TimerTask task) {
// 擴(kuò)容邏輯(當(dāng)數(shù)組滿時(shí))
if (size + 1 == queue.length)
queue = Arrays.copyOf(queue, 2 * queue.length);
queue[size++] = task;
// 上浮調(diào)整:維持小頂堆特性
fixUp(size - 1);
}
// 獲取堆頂任務(wù)(最早執(zhí)行的任務(wù))
TimerTask getMin() {
return queue[0];
}
// 移除堆頂任務(wù)(出隊(duì))
void removeMin() {
queue[0] = queue[--size];
// 下沉調(diào)整:維持小頂堆特性
fixDown(0);
}
// 上浮操作:新元素插入后,向上調(diào)整堆
private void fixUp(int k) {
while (k > 0) {
int j = (k - 1) >>> 1; // 父節(jié)點(diǎn)下標(biāo)
if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
break; // 父節(jié)點(diǎn)更小,無需調(diào)整
// 交換父節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)
TimerTask tmp = queue[j];
queue[j] = queue[k];
queue[k] = tmp;
k = j;
}
}
// 下沉操作:移除堆頂后,向下調(diào)整堆
private void fixDown(int k) {
int j;
while ((j = (k << 1) + 1) < size) { // 左子節(jié)點(diǎn)下標(biāo)
// 找到左右子節(jié)點(diǎn)中較小的那個(gè)
if (j + 1 < size && queue[j + 1].nextExecutionTime < queue[j].nextExecutionTime)
j++;
if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
break; // 當(dāng)前節(jié)點(diǎn)更小,無需調(diào)整
// 交換當(dāng)前節(jié)點(diǎn)和子節(jié)點(diǎn)
TimerTask tmp = queue[k];
queue[k] = queue[j];
queue[j] = tmp;
k = j;
}
}
}調(diào)度線程(TimerThread):任務(wù)執(zhí)行的核心邏輯
TimerThread 是 Timer 的內(nèi)部線程類,負(fù)責(zé)從隊(duì)列中取任務(wù)并執(zhí)行:
class TimerThread extends Thread {
private final TaskQueue queue;
TimerThread(TaskQueue queue) {
this.queue = queue;
}
public void run() {
try {
mainLoop(); // 核心循環(huán):不斷執(zhí)行任務(wù)
} finally {
// 線程退出時(shí),標(biāo)記所有任務(wù)為取消狀態(tài)
synchronized(queue) {
queue.isDisposed = true;
queue.queue = null;
}
}
}
// 核心循環(huán):調(diào)度任務(wù)的主邏輯
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
// 同步獲取任務(wù)
synchronized(queue) {
// 等待隊(duì)列中有任務(wù)
while (queue.size() == 0 && !queue.isDisposed)
queue.wait();
if (queue.isDisposed)
break; // 隊(duì)列已銷毀,退出循環(huán)
// 獲取堆頂任務(wù)(最早執(zhí)行的任務(wù))
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
if (task.state != TimerTask.SCHEDULED) {
queue.removeMin(); // 任務(wù)已取消,移除
continue;
}
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
// 檢查是否到達(dá)執(zhí)行時(shí)間
if (executionTime > currentTime) {
// 未到時(shí)間:等待差值時(shí)間
queue.wait(executionTime - currentTime);
taskFired = false;
} else {
// 已到時(shí)間:移除堆頂任務(wù)
queue.removeMin();
taskFired = true;
}
}
}
// 執(zhí)行任務(wù)(如果已到時(shí)間)
if (taskFired) {
task.run(); // 調(diào)用TimerTask的run()方法
// 處理周期性任務(wù):重新計(jì)算下次執(zhí)行時(shí)間并加入隊(duì)列
if (task.period != 0) {
synchronized(task.lock) {
task.nextExecutionTime =
task.period < 0 ? currentTime - task.period
: executionTime + task.period;
queue.add(task); // 重新入隊(duì)
}
}
}
} catch (InterruptedException e) {
// 忽略中斷,繼續(xù)循環(huán)
}
}
}
}代碼使用演示
package com.ape.test;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Test {
public static void main(String[] args) {
Timer timer = new Timer();
final int totalTasks = 3; // 總?cè)蝿?wù)數(shù)
for(int i = 0; i < totalTasks; i++){
TimerTask task = new FooTimerTask("task" + i, timer, totalTasks);
timer.schedule(task, new Date());
}
}
}
class FooTimerTask extends TimerTask{
private String name;
private Timer timer;
private int totalTasks;
private static int completedCount = 0; // 已完成任務(wù)計(jì)數(shù)(靜態(tài)變量共享)
public FooTimerTask(String name, Timer timer, int totalTasks) {
this.name = name;
this.timer = timer;
this.totalTasks = totalTasks;
}
@Override
public void run() {
try {
System.out.println("name:" + name + " startTime=" + new Date());
Thread.sleep(3000);
System.out.println("end:" + name + " endTime=" + new Date());
System.out.println("----------------------------------------");
// 任務(wù)完成后計(jì)數(shù),最后一個(gè)任務(wù)完成時(shí)取消Timer,如果不取消Timer,進(jìn)程不會結(jié)束
synchronized (FooTimerTask.class) {
completedCount++;
if (completedCount == totalTasks) {
timer.cancel();
System.out.println("所有任務(wù)執(zhí)行完畢,Timer已終止");
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
運(yùn)行結(jié)果

Timer的缺點(diǎn)
單線程串行執(zhí)行,效率低所有任務(wù)依賴唯一的
TimerThread執(zhí)行,若某個(gè)任務(wù)耗時(shí)過長(如 IO 阻塞、長時(shí)間計(jì)算),會阻塞后續(xù)所有任務(wù),即使它們已到執(zhí)行時(shí)間。例:任務(wù) A 執(zhí)行需 10 秒,任務(wù) B 本應(yīng)在 2 秒后執(zhí)行,實(shí)際會延遲到 A 結(jié)束后才開始。異常敏感,易導(dǎo)致整體崩潰若某個(gè)
TimerTask的run()方法拋出未捕獲異常,會直接導(dǎo)致TimerThread終止,所有未執(zhí)行的任務(wù)全部失效,且不會有任何提示。時(shí)間依賴系統(tǒng)時(shí)鐘,穩(wěn)定性差任務(wù)的執(zhí)行時(shí)間依賴系統(tǒng)時(shí)間,若系統(tǒng)時(shí)間被手動調(diào)整(如向前回?fù)埽?,可能?dǎo)致任務(wù)執(zhí)行混亂(如周期性任務(wù)被重復(fù)執(zhí)行)。
不支持多線程并發(fā)無法利用多核 CPU 資源,所有任務(wù)只能串行執(zhí)行,不適合高并發(fā)或任務(wù)量大的場景。
線程生命周期管理繁瑣
Timer線程默認(rèn)是用戶線程,若不主動調(diào)用timer.cancel(),即使所有任務(wù)執(zhí)行完畢,線程也會一直運(yùn)行,導(dǎo)致程序無法退出。
適用場景與替代方案
- 適合場景:單線程、任務(wù)執(zhí)行時(shí)間短、無并發(fā)需求的簡單定時(shí)任務(wù)(如簡單的延遲提醒、臨時(shí)調(diào)度)。
- 不適合場景:多任務(wù)并發(fā)、任務(wù)耗時(shí)較長、需要高穩(wěn)定性的場景(如分布式定時(shí)任務(wù)、核心業(yè)務(wù)調(diào)度)。
- 推薦替代方案:
ScheduledExecutorService,基于線程池實(shí)現(xiàn),解決了Timer的單線程瓶頸和異常敏感問題,支持多線程并行執(zhí)行,是更現(xiàn)代、更健壯的選擇。
在下一篇文章中說說ScheduleExecutorService
總結(jié)
到此這篇關(guān)于Java Timer單線程下的定時(shí)任務(wù)的文章就介紹到這了,更多相關(guān)Java Timer單線程定時(shí)任務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis執(zhí)行錯誤但sql執(zhí)行正常問題
這篇文章主要介紹了mybatis執(zhí)行錯誤但sql執(zhí)行正常問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
SpringBoot?整合mapstruct的實(shí)現(xiàn)步驟
這篇文章主要介紹了SpringBoot整合mapstruct,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Mybatis-Plus條件構(gòu)造器select方法返回指定字段方式
這篇文章主要介紹了Mybatis-Plus條件構(gòu)造器select方法返回指定字段方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
Java?Servlet響應(yīng)httpServletResponse過程詳解
HttpServletResponse是處理http響應(yīng)的對象,調(diào)用該對象的方法,設(shè)置到對象屬性的內(nèi)容,tomcat最終會組織為http響應(yīng)報(bào)文2022-02-02

