Java簡易定時任務實現

前言

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

最終的實現效果

// 具體的業務
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();
}

類圖

類圖

各個類的做用

項目地址git

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方法時,隊列會阻塞,直到有延遲到期的元素纔會返回。github

總結

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

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

如今的實現方式是和Java的延遲隊列綁定了的,可是延遲隊列有多種實現方式,
例如redisrabbitMQ等,若是可以作出更高級的抽象,合入不一樣的延遲隊列那會更好。
此外這種實現方式性能方面也有待驗證。微信

相關文章
相關標籤/搜索