1. 程式人生 > >定時任務類(原始碼研究)

定時任務類(原始碼研究)


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

 

接下來繼續看