Java簡易定時任務實現

前言

接入微信支付的時候,看到微信支付的回調是按照某種頻率去回調的,
15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h這樣,其中有一次成功就不會再回調。
於是在想怎麼用Java做這個事情。
有定時任務這類功能的框架像SpringQuartz貌似都沒有直接提供以上的功能。
也是出於想練手的目的,決定自己寫一寫。

最終的實現效果

// 具體的業務
BaseJob task = new BaseJob() {
    
    // 任務執行的次數(模擬真實業務上的退出)
    int runTime = 1;
    
    @Override
    public void run() {
        
        // 業務邏輯
        System.out.println("hello world");

        // 這裏模擬了微信回調成功,任務完成
        if (runTime++ > 3) {
            this.setExit(true);
        }
    }
};
/**
 * 測試按照指定時間隔執行某個任務
 * @throws IOException
 */
@Test
public void test1() throws IOException {
    
    // 新建一個產生指定時間的延遲時間生成器,內部就是個隊列
    DesignatDTGenerator designatDTGenerator = new DesignatDTGenerator();
    
    // 設置時間間隔
    designatDTGenerator.addDelayTime(1_000) // 1秒後執行
                       .addDelayTime(4_000) // 距離上次執行4秒後執行
                       .addDelayTime(15_000) // 距離上次執行15秒後執行
                       .addDelayTime(180_000) // 距離上次執行3分鐘後執行
                       .addDelayTime(180_000) // 距離上次執行3分鐘後執行
                       .addDelayTime(360_000) // 距離上次執行6分鐘後執行
                       .addDelayTime(3_600_000); // 距離上次執行1小時後執行
        
    // 構造一個提交的任務,傳入具體的業務對象task,傳入延遲時間生成器designatDTGenerator
    DelayTimeJob delayTimeJob = new DelayTimeJob(task, designatDTGenerator);
    
    // 新建一個執行器,執行器可以重複使用,每次提交新的任務即可
    JobActuator actuator = new JobActuator(); 
    
    // 提交任務,開始執行任務
    actuator.addJob(delayTimeJob);
    
    // 阻塞主線程,方便查看運行結果
    System.in.read();
}
/**
 * 測試按照固定時間間隔執行某個任務
 * 只是延遲時間生成器不同而已,可以達到不同的調用效果
 * @throws IOException
 */
@Test
public void test2() throws IOException {
    
    // 新建一個執行器
    JobActuator actuator = new JobActuator(); 
    
    // 新建一個產生固定時間的延遲時間生成器,每3s執行一次
    FixedRateDTGenerator fixedRateDTGenerator = new FixedRateDTGenerator(3000);
    
    // 新建一個任務
    DelayTimeJob delayTimeJob = new DelayTimeJob(task, fixedRateDTGenerator);

    // 提交任務,開始執行任務
    actuator.addJob(delayTimeJob);
    
    // 阻塞主線程,方便查看運行結果
    System.in.read();
}

類圖

類圖

各個類的作用

項目地址

JobActuator
任務執行器,本身繼承了Thread,職責是在run方法中不斷從延遲任務隊列DelayQueue中獲取延遲到期的任務,
再交由線程池ExecutorService執行。延遲效果的都是依靠DelayQueue實現。
public class JobActuator extends Thread {

    /** 線程池 */
    ExecutorService es = Executors.newFixedThreadPool(2);
    
    /** 任務隊列 */
    DelayQueue<DelayTimeJob> jobs = new DelayQueue<>();
    
    /** 構造方法,實例化時啓動線程 */
    public JobActuator() {
        this.start();
    }
    
    public void addJob(DelayTimeJob job) {
        // 設置任務隊列,用於任務重新入隊
        job.setJobs(jobs);
        // 任務入隊
        jobs.offer(job);
    }
    
    @Override
    public void run() {
     
        while (true) {
            
            try {
                // 從延遲隊列中獲取任務
                DelayTimeJob job = jobs.take();
                // 利用線程池執行任務
                es.submit(job);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}
DelayTimeJob
實現了Delayed接口,執行實際的業務並決定任務是否重新進入延遲隊列。
public class DelayTimeJob implements Runnable, Delayed {
    
    /** 執行器的任務隊列,用於任務重新入隊 */
    @Setter
    private DelayQueue<DelayTimeJob> jobs;

    /** 延遲時間生成器 */
    IDelayTimeGenerator delayTimeGenerator;
    
    /** 具體要執行的任務 */
    private BaseJob realJob;
    
    private long time = 0L;
    
    public DelayTimeJob(BaseJob baseJob, IDelayTimeGenerator delayTimeGenerator) {
        
        this.realJob = baseJob;
        this.delayTimeGenerator = delayTimeGenerator;
        
        Integer delayTime = delayTimeGenerator.getDelayTime();
        if (delayTime == null) {
            return ;
        }
        
        this.time = delayTime + System.currentTimeMillis();
    }
    
    @Override
    public void run() {
        
        // 執行業務
        realJob.run();
        
        // 任務不再需要執行,主動退出
        if (realJob.isExit) {
            return ;
        }
        
        // 獲取延遲 
        Integer delayTime = delayTimeGenerator.getDelayTime();
        
        // 無延遲時間,則任務不再執行
        if (delayTime == null) {
            return ;
        }
        
        // 重新入隊
        time += delayTime;
        jobs.offer(this);
        return ;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(this.time - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        
        DelayTimeJob other = (DelayTimeJob) o;  
        long diff = time - other.time;  
        
        if (diff > 0) {  
            return 1;  
        } 
        if (diff < 0) {  
            return -1;  
        }
        return 0;
    }
    
}
BaseJob
用戶繼承此抽象類,在run方法中編寫業務代碼,通過控制isExit變量控制任務是否執行。
public abstract class BaseJob implements Runnable {

    /** 用於控制任務是否退出 */
    @Setter
    boolean isExit = false;
    
}
IDelayTimeGenerator
延遲時間生成器接口,返回一個延遲時間。可以實現不同的策略,達到不同的延遲效果。
DesignatDTGenerator是定義每一次執行的時間間隔,FixedRateDTGenerator是按照某一個固定頻率執行。
public interface IDelayTimeGenerator {
    
    /** 返回延遲的時間,單位:毫秒 */
    Integer getDelayTime();
    
}
/**
 * 指定時間的時間生成器
 * @author cck
 */
public class DesignatDTGenerator implements IDelayTimeGenerator {

    private final Deque<Integer> delayTimeQueue = new ArrayDeque<>();
    
    /**
     * 添加延遲時間
     * @param delayTime
     */
    public DesignatDTGenerator addDelayTime(Integer delayTime) {
        delayTimeQueue.offer(delayTime);
        return this;
    }
    
    @Override
    public Integer getDelayTime() {
        return delayTimeQueue.poll();
    }

}
/**
 * 固定間隔的時間生成器
 * @author cck
 */
public class FixedRateDTGenerator implements IDelayTimeGenerator {

    private Integer delayTime;
    
    public FixedRateDTGenerator(Integer delayTime) {
        this.delayTime = delayTime;
    }
    
    @Override
    public Integer getDelayTime() {
        return delayTime;
    }

}

關鍵類DelayQueueDelayed

DelayQueueJava提供的延遲隊列,該隊列只允許實現了Delayed接口的對象入隊。
調用隊列的take方法時,隊列會阻塞,直到有延遲到期的元素纔會返回。

總結

這個方式是可以實現一開始想要的按照15s/15s/30s/3m/10m/..指定的間隔執行任務的效果的。
定製延遲的效果只需要給出不同的IDelayTimeGenerator接口實現即可。

在和spring一起使用時,任務執行器JobActuator應該是單例的,
不過提交任務的整個操作相比於spring的一個註解,還是顯得麻煩囧,使用時再封裝一層會更好。

現在的實現方式是和Java的延遲隊列綁定了的,但是延遲隊列有多種實現方式,
例如redisrabbitMQ等,如果能夠做出更高級的抽象,合入不同的延遲隊列那會更好。
此外這種實現方式性能方面也有待驗證。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章