本文通過完整代碼示例,演示如何使用 Timer 與 TimerTask 實現每日凌晨 2 點自動執行業務邏輯,並解決服務重啟導致任務立即執行的經典問題。
public class TimerManager {
private static final long PERIOD_DAY = 24 * 60 * 60 * 1000; // 24小時毫秒數
public TimerManager() {
Calendar calendar = Calendar.getInstance();
// 設定每日 02:00:00 執行
calendar.set(Calendar.HOUR_OF_DAY, 2);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date firstRunTime = calendar.getTime();
// 關鍵邏輯:若首次執行時間早於當前時間,順延一天
if (firstRunTime.before(new Date())) {
firstRunTime = addDay(firstRunTime, 1);
}
Timer timer = new Timer();
NFDFlightDataTimerTask task = new NFDFlightDataTimerTask();
// 安排任務:首次執行 firstRunTime,之後每 PERIOD_DAY 執行一次
timer.schedule(task, firstRunTime, PERIOD_DAY);
}
// 日期加減工具方法
public Date addDay(Date date, int days) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DAY_OF_MONTH, days);
return cal.getTime();
}
}
若服務在凌晨 2 點後啟動(如 3 點),firstRunTime (2點) 會早於當前時間,若不處理,任務會立即執行而非等待次日。代碼中的 if (date.before(new Date())) 判斷正是為此:檢測到時間已過,自動將首次執行時間順延 24 小時,確保任務在次日 2 點準時運行。
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) {
// 容器關閉時可執行清理工作
}
}
<listener>
<listener-class>
com.listener.NFDFlightDataTaskListener
</listener-class>
</listener>
run() 方法必須捕獲所有異常,否則 Timer 線程會終止,導致後續任務不再執行ScheduledThreadPoolExecutor 替代 Timer