定時任務類(原始碼研究)
阿新 • • 發佈:2018-12-21
package situopenapi.service.common; import situopenapi.utils.LogUtils; import situopenapi.utils.ThreadFactory; import javax.annotation.PostConstruct; import java.io.IOException; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * 封裝了定時更新的邏輯,子類只需要指定更新間隔,並實現{@link #update()}方法即可。 * 如果使用spring annotation進行api管理,直接使用autowired的例項即可; * 如果使用spring xml初始化api物件,需要配置init-method="init"; * 如果直接使用new生成物件,需要new之後呼叫init方法初始化。 * <p/> * <b>注意</b>:在{@link #update()}方法中執行的程式碼,在伺服器宕機等情況下會被打斷, * 因此請務必考慮被打斷的後果,必要的時候可增加事務等額外機制保證資料完整性。 * <p/> * <b>注意</b>:包含PostConstruct方法的類,若出現在迴圈引用的鏈條上,會導致在未完成自動注入的情況下, * PostConstruct方法被執行,若此時PostConstruct方法恰好使用了未完成注入的欄位就會拋空指標錯誤。 * {@link ScheduledFixationService}中包含PostConstruct方法,因此使用時需要格外小心。 * 在必要的情況下,可以僅保留{@link ScheduledFixationService}的{@link #update()}方法,最大限度的避免其他類引用該Service, * 從而防止該問題的發生。 * * @author LIU Shufa */ public abstract class ScheduledFixationService extends BaseService { private static final ScheduledThreadPoolExecutor SCHEDULER = new ScheduledThreadPoolExecutor( 20, new ThreadFactory("ScheduledService", true)); private static final AtomicInteger RUNNING_TASK_COUNT = new AtomicInteger(0); /** * 一個可以停止所有定時更新的開關,以便在重啟服務的時候暫時停止定時更新 */ private static volatile boolean runnable = true; /** * 在定時更新停止後是否需要輸出定時更新停止警告 */ private static volatile boolean scheduleStoppedWarning = true; /** * 是否在啟動時同步執行一次update * <p/> * 一般情況下需要在啟動時同步執行一次update,以便在啟動後快取的資料是正確的, * 但這樣會增加啟動時間,部分耗時的定時任務可以不必在啟動時執行 * * @return 是否需要在啟動時同步執行一次update */ protected boolean runPreUpdate() { return false; } /** * 更新的具體程式碼,子類通過實現該方法實現定時更新。 */ public abstract void update() throws IOException; /** * 返回定時更新的時間間隔,子類通過實現該方法指定更新的時間間隔 * * @return 快取主動更新的時間間隔,單位是秒 */ public abstract long updatePeriod(); public abstract long updateInitialDelay(); /** * 防止誤呼叫導致的多次初始化 */ private volatile boolean alreadyInit = false; /** * 獲取當前正在執行的定時任務的數量,以便判斷是否可以重啟伺服器 * * @return */ public static int runningTaskCount() { return RUNNING_TASK_COUNT.get(); } /** * 啟動或者暫定所有的定時任務,使用時務必小心! * * @param value */ public static void setRunnable(boolean value) { runnable = value; } public static void setScheduleStoppedWarning(boolean value) { scheduleStoppedWarning = value; } /** * 初始化定時更新任務,初始化過程中會同步呼叫一次{@link #update()} */ @PostConstruct public synchronized void init() { if (alreadyInit) { return; } long period = updatePeriod(); long initialDelay = updateInitialDelay(); if (runPreUpdate()) { updateCacheWithoutException(); } SCHEDULER.scheduleAtFixedRate(() -> updateCacheWithoutException(), initialDelay, period, TimeUnit.MILLISECONDS); alreadyInit = true; } private void updateCacheWithoutException() { if (!runnable) { if (scheduleStoppedWarning) { LogUtils.info("ScheduledService: schedule service stop!"); } return; } RUNNING_TASK_COUNT.incrementAndGet(); try { update(); } catch (Exception e) { // 異常不應該影響後續執行 e.printStackTrace(); } finally { RUNNING_TASK_COUNT.decrementAndGet(); } } }
private static final ScheduledThreadPoolExecutor SCHEDULER = new ScheduledThreadPoolExecutor( 20, new ThreadFactory("ScheduledService", true));
關於這個ScheduledThreadPoolExecutor 是一個自帶的java類,它可另行安排在給定的延遲後執行命令,或者定期執行命令。需要多個輔助執行緒時,或者要求 ThreadPoolExecutor 具有額外的靈活性或功能時,此類要優於Timer。
使用Timer和TimerTask存在一些缺陷:
1.Timer只建立了一個執行緒。當你的任務執行的時間超過設定的延時時間將會產生一些問題。
2.Timer建立的執行緒沒有處理異常,因此一旦丟擲非受檢異常,該執行緒會立即終止。
(所以如果想實現在某個特定的時間進行定時更新的話,其實也就是利用延遲更新的辦法,否則在spring專案啟動的過程中,就會進行更新處理)
@PostConstruct
因為該定時任務包含了PostConstruct方法,所以使用的時候需要格外小心。
LINK:https://blog.csdn.net/pmdream/article/details/84937023
接下來繼續看