定時任務,在平常工做中,能夠說是一個算是一個常見的需求場景,好比定時數據校驗,數據報表輸出,報警等java
前面一篇博文《Java併發學習之四種線程建立方式的實現與對比》, 有朋友指出線程池的方式應該算不上新的方式,而應該把Timer方式建立線程加上spring
這個倒是我我的見識不夠,寫的時候沒有想到Timer這種場景了,因此說分享學習記錄,不只僅能夠幫助別人,本身也會所以收益併發
感謝@超大小龍蝦 的指正,同時歡迎各位大俠對小弟多多指教框架
這裏給出幾種我的接觸過的定時任務使用方式(不全,僅供你們參考)異步
Thread.sleep()
,休眠掛起線程,等待一段時間後再執行Executors.newScheduledThreadPool()
實現定時任務下面簡單介紹上面的幾種思路,以及通常的使用姿式ide
嚴格來說,這種不太可以算入定時任務的範疇,爲何這麼說?工具
通常咱們所說的定時任務能夠區分爲兩種,一種是到了某個點自動執行;另外一種就是每隔多長時間執行一次學習
而這種線程Sleep的方式,則是在運行後,強制使線程進入阻塞狀態一段時間,而後再執行後續的邏輯,通常的使用流程是.net
// 提早的業務邏輯 xxx try { Thread.sleep(1000); // 睡眠1s } catch(Exception e) { // .... } // 定時任務的業務邏輯 // xxx
這裏把這個也放在定時任務裏,能夠看下面結合實例的case中的演示,利用這個sleep也能夠很是猥瑣的實現定時需求線程
TimerTask 是一個實現 Runnable的抽象類,所以能夠將須要定時處理的業務邏輯封裝在這個Task裏面;而後經過Timer封裝類來定時調度
TimerTask的使用姿式和通常的Runnable
接口沒啥兩樣
通常使用姿式以下
// 建立timer實例 Timer timer = new Timer("demo); // 撰寫定時任務邏輯 TimerTask task = new TimerTask() { @Override public void run() { System.out.println("timerTask: " + System.currentTimeMillis()); } }; // 定時調度執行 // 1. 100ms後開始執行task任務 timer.schedule(task, 100); // 2. 100ms後,首次執行,而且每隔100ms執行一次task任務 timer.scheduleAtFixedRate(task, 100, 100);
這個就有意思一點了,能夠支持定時執行,也能夠支持按一個頻率執行,且通常使用能夠將上面的步驟進行縮減, 直接這麼玩
new Timer("schedule").schedule(new TimerTask() { @Override public void run() { System.out.println("timerTask: " + System.currentTimeMillis()); } }, 100);
Executors#newScheduledThreadPool
線程池方式
Executors
提供了一批建立線程池的方式,除了常見的建立固定大小的線程池以外,還有個一就是建立ScheduledExecutorService
來實現定時任務調度
藉助Executors#newScheduledThreadPool
來實現定時任務很是簡單
// 獲取線程池 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); // 延遲100ms後,執行定時任務 executorService.schedule(new Runnable() { @Override public void run() { System.out.println("task: " + System.currentTimeMillis()); } }, 100, TimeUnit.MILLISECONDS); // 100ms後,首次執行,而後每一個100ms執行一次 executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("task: " + System.currentTimeMillis()); } }, 100, 100, TimeUnit.MILLISECONDS)
從使用姿式來看,和Timer方式差不離,一樣支持定時執行與每隔多長時間執行兩種方式
spring方式就很是強大了,並且支持註解的配置方式,配置完畢,而後在方法上加一個註解,就能夠實現定時執行了
常見的使用姿式
// 100ms後執行 @Scheduled(fixedDelay = 100) public void doSomething() { // something that should execute periodically } // 每隔100ms執行一次 @Scheduled(fixedRate = 100) public void doSomething() { // something that should execute periodically }
並且比較厲害的是,這個還支持cron表達式
來兩個實際的應用場景,用上面的四種方式分別實現
case:
系統中有一些統計數據,須要離線計算,天天凌晨計算完以後導入,而後須要一個定時任務,假設凌晨5點數據導入完畢;在5:15分進行校驗,判斷數據是否正常導入;校驗完成以後,45分鐘後即六點,將校驗結果通知給owner
採用sleep的方式實現定時任務,由於其自己不支持定時的狀況,因此就只能比較猥瑣的計算須要sleep的時間了
實現代碼以下(非精確的實現方式,主要爲了演示如何用sleep來實現上面這種場景)
public class SleepDemo { static class Task implements Runnable { public void run() { while (true) { Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); int min = calendar.get(Calendar.MINUTE); int sleepHour, sleepMin; // 計算sleep的小時數; 若在啓動時,在五點前 5-hour; 不然須要加一天 sleepHour = 5 - hour < 0 ? (24 + 5 - hour) : (5 - hour); sleepMin = 15 - min; // 計算sleep的分鐘數 try { long sleepTime = ((sleepHour * 60) + sleepMin) * 60 * 1000L; Thread.sleep(sleepTime); // 開始校驗數據是否存在 System.out.println("數據校驗"); // 等待到6點,開始報警 int second = calendar.get(Calendar.SECOND); sleepTime = ((59 - calendar.get(Calendar.MINUTE)) * 60 + 60 - second) * 1000L; Thread.sleep(sleepTime); System.out.println("開始報警"); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { new Thread(new Task(),"sleepDemo").start(); } }
簡單說明下上面的實現思路:
while(true)
死循環,來實現每隔多長時間來執行一次這個實現方式,雖然說能夠完成目標,可是很是的不優雅,下面來看下Timer的實現方式
使用Timer,須要藉助TimerTask類,在其中書寫定時任務的邏輯,由於case中有一個每隔一天跑一次的定時任務和一個延遲任務,因此這裏用到了Timer的兩種定時任務使用方式
public class TimerDemo { static class Task extends TimerTask { @Override public void run() { System.out.println("開始執行任務"); // 執行完畢,等待到6點發送報警 int min = Calendar.getInstance().get(Calendar.MINUTE); int sec = Calendar.getInstance().get(Calendar.SECOND); long delayTime = ((59 - min) * 60 + 60 - sec) * 1000L; new Timer().schedule(new TimerTask() { @Override public void run() { System.out.println("報警"); } }, delayTime); } } public static void main(String[] args) { Date date = new Date(); if (date.getHours() == 5 && date.getMinutes() > 15 || date.getHours() > 5) { date.setHours(5); } else { date.setMinutes(15); } date.setSeconds(0); new Timer().scheduleAtFixedRate(new Task(), date, 24 * 2600 * 1000L); } }
相比與上一個,稍微好了那麼一丟丟,至少從代碼結構上來看簡潔了不少
定時任務的方式,用起來和前面差很少,依然是兩種方式的混搭
public class ScheduleDemo { static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2); static class Task extends TimerTask { @Override public void run() { System.out.println("開始執行任務"); // 執行完畢,等待到6點發送報警 int min = Calendar.getInstance().get(Calendar.MINUTE); int sec = Calendar.getInstance().get(Calendar.SECOND); long delayTime = (59 - min) * 60 + 60 - sec; executorService.schedule(() -> System.out.println("報警"), delayTime, TimeUnit.SECONDS); } } public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); // 計算sleep的小時數 int sleepHour = 5 - calendar.get(Calendar.HOUR_OF_DAY); if(sleepHour < 0) { // 算下一天 sleepHour = 24 + sleepHour; } // 計算sleep的分鐘數 int sleepMin = 15 - calendar.get(Calendar.MINUTE); long sleepTime = ((sleepHour * 60) + sleepMin) * 60 * 1000L; executorService.scheduleAtFixedRate(new Task(), sleepTime, 24 * 3600, TimeUnit.SECONDS); } }
與Timer在用法上不一樣的一個是這裏能夠指定延遲的時間單位;可是但願在指定的時間進行執行時,依然仍是得計算初始的延遲時間,和sleep使用方式中差很少
上面三中,是jdk自己就支持的定時任務的支持;總得來講,能實現你的需求場景,可是很差用,還得讓本身去計算delayTime/sleepTime;講道理,這對使用者而言,實在是不能更不友好了;
可是在另外一方面,若延遲時間比較容易確認的話;或者單純的使用每隔多長時間調度一次的話,Timer
和ScheduledExecutorService
兩種方式都還不錯
Spring 相比較jdk自帶的幾種方式而言,我認爲,最完美的有兩點
配置xml文件
<task:executor id="executor" pool-size="5" /> <task:scheduler id="scheduler" pool-size="10" /> <task:annotation-driven executor="executor" scheduler="scheduler" />
實現業務邏輯
@Component public class ScheduleDemo { @Scheduled(cron = "0 0 5 * * ?") public void doSome() { System.out.println(" 校驗: " + System.currentTimeMillis()); } @Scheduled(cron = "0 0 6 * * ?") public void alarm(){ System.out.println(" 報警: " + System.currentTimeMillis()); } }
這個實現就簡單了,相比較上面而言,添加一個註解,裏面配置cron表達式,xml配置下,就能夠實現定時任務
方式 | 說明 | 特色 |
---|---|---|
Thread#sleep | 線程掛起一段時間 | 經過定時目標與當前時間計算sleepTime,來強制實現定時任務 |
Timer#TimerTask | 異步定時任務 | TimerTask內部實現定時任務邏輯 <br/>1. Timer可按頻率調度任務 <br/> 2. Timer也支持指定時間調度任務 |
ScheduledExecutorService | 計劃任務線程池 | 1. 利用Executors#newScheduledThreadPool;建立線程池 <br/> 2. 建立線程任務實現定時任務邏輯 <br/> 3. 提交線程池執行,支持按頻率調度,支持延遲多長時間調度 <br/> 4. 支持獲取返回值 |
Spring Schedule | spring提供的定時任務 | 支持cron表達式,使用簡單,很是簡單,超級簡單 |
不推薦使用 Thread#sleep的方式作定時任務
如指向利用jdk實現定時任務,能夠考慮 Timer
和 ScheduledExecutorService
如項目自己就利用到了Spring,能夠優先考慮這些優秀的框架提供的服務,用起來特別爽,誰用誰知道
盡信書則不如,已上內容,純屬一家之言,因本人能力通常,見識有限,若有問題,請不吝指正,感激