前言
接入微信支付的時候,看到微信支付的回調是按照某種頻率去回調的,
像15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h
這樣,其中有一次成功就不會再回調。
於是在想怎麼用Java
做這個事情。
有定時任務這類功能的框架像Spring
和Quartz
貌似都沒有直接提供以上的功能。
也是出於想練手的目的,決定自己寫一寫。
最終的實現效果
// 具體的業務
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;
}
}
關鍵類DelayQueue
和Delayed
DelayQueue
是Java
提供的延遲隊列,該隊列只允許實現了Delayed
接口的對象入隊。
調用隊列的take
方法時,隊列會阻塞,直到有延遲到期的元素纔會返回。
總結
這個方式是可以實現一開始想要的按照15s/15s/30s/3m/10m/..
指定的間隔執行任務的效果的。
定製延遲的效果只需要給出不同的IDelayTimeGenerator
接口實現即可。
在和spring
一起使用時,任務執行器JobActuator
應該是單例的,
不過提交任務的整個操作相比於spring
的一個註解,還是顯得麻煩囧,使用時再封裝一層會更好。
現在的實現方式是和Java
的延遲隊列綁定了的,但是延遲隊列有多種實現方式,
例如redis
,rabbitMQ
等,如果能夠做出更高級的抽象,合入不同的延遲隊列那會更好。
此外這種實現方式性能方面也有待驗證。