在 Java 開發中,定時任務是常見需求:如每日清理日誌、定時同步數據、發送報表等。本文將通過一個完整的「每日凌晨 2 點執行」案例,詳細講解如何使用 Timer 和 TimerTask 實現每日定時任務,並重點分析「首次執行時間早於當前時間」導致任務立即執行的經典坑及其解決方案。
public class TimerManager {
// 時間間隔:一天(毫秒)
private static final long PERIOD_DAY = 24 * 60 * 60 * 1000;
public TimerManager() {
Calendar calendar = Calendar.getInstance();
// 設定每日 2:00 執行
calendar.set(Calendar.HOUR_OF_DAY, 2);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date date = calendar.getTime(); // 首次執行時間
// 關鍵判斷:若首次執行時間早於當前時間,則順延一天
if (date.before(new Date())) {
date = this.addDay(date, 1);
}
Timer timer = new Timer();
NFDFlightDataTimerTask task = new NFDFlightDataTimerTask();
// 安排任務:指定時間開始,固定延遲重複執行
timer.schedule(task, date, PERIOD_DAY);
}
// 輔助方法:增加/減少天數
public Date addDay(Date date, int num) {
Calendar startDT = Calendar.getInstance();
startDT.setTime(date);
startDT.add(Calendar.DAY_OF_MONTH, num);
return startDT.getTime();
}
}
public class NFDFlightDataTimerTask extends TimerTask {
private static Logger log = Logger.getLogger(NFDFlightDataTimerTask.class);
@Override
public void run() {
try {
// 在此編寫具體業務邏輯
// 例如:數據同步、報表生成、緩存清理等
} catch (Exception e) {
log.info("-------------解析信息發生異常--------------");
}
}
}
public class NFDFlightDataTaskListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
new TimerManager(); // 應用啟動時啟動定時任務
}
public void contextDestroyed(ServletContextEvent event) {
// 可在此添加銷毀邏輯,如取消 timer
}
}
<listener>
<listener-class>
com.listener.NFDFlightDataTaskListener
</listener-class>
</listener>
這是本例中最核心、最容易踩坑的地方。
date(今天 02:00)早於當前時間(今天 03:00),timer.schedule 會認為「首次執行時間已過」,導致任務立即執行一次,而非等待明天 02:00。if (date.before(new Date())) date = addDay(date, 1); 將首次執行時間順延至明天 02:00,完美規避此問題。雖然 Timer 簡單易用,但在高併發、長時間運行的生產環境中,建議考慮:
本文通過完整代碼演示了使用 Timer 實現每日定時任務的標準流程,重點剖析了「首次執行時間校正」這一關鍵邏輯。掌握此機制,能讓你在維護舊項目或輕量級場景下游刃有餘;而在新項目中,請優先擁抱 ScheduledExecutorService 或成熟調度框架。