写在前面
目前项目中有这样一个场景:需要设置一个倒计时,倒计时结束后更新数据库中某个表状态。
于是乎,我就想到了使用延时队列来处理这种场景。
直接上代码
定义任务类
/**
* 延时任务类
*/
public abstract class BusiTask implements Delayed, Runnable {
//唯一标识
private String taskId;
//任务名称
private String taskName;
//延时到多久
private long endTime;
//定义时间工具类
private TimeUnit timeUnit = TimeUnit.SECONDS;
//传进来的是延时的秒数
public BusiTask(String taskId, String taskName, long endSecond) {
this.taskId = taskId;
this.taskName = taskName;
this.endTime = System.currentTimeMillis() + endSecond * 1000;
}
/**
* 用来判断是否到了截止时间
*/
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(endTime, TimeUnit.MILLISECONDS) - unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
//return endTime - System.currentTimeMillis();
}
/**
* 相互批较排序用
*/
@Override
public int compareTo(Delayed o) {
BusiTask o1 = (BusiTask)o;
return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
//return this.getDelay(this.timeUnit) - t.getDelay(this.timeUnit) > 0 ? 1:0;
}
// 异步执行任务
@Override
public void run() {
excute();
}
/**
* 具体执行的任务,需要实现该方法
*/
public abstract void excute();
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public long getEndTime() {
return endTime;
}
public void setEndTime(long endTime) {
this.endTime = endTime;
}
}
任务池
import java.util.Date;
import java.util.Random;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.locks.ReentrantLock;
//延迟队列测试
public class BusiExcuteTask implements Runnable {
//唯一延时队列
private final static DelayQueue<BusiTask> queue = new DelayQueue<BusiTask>();
// 锁,任务池状态更改需要加锁
private final ReentrantLock lock = new ReentrantLock();
// 初始化任务池
private static BusiExcuteTask busiExcuteTask = new BusiExcuteTask();
//状态使用volatile线程同步
// 任务池线程线程启动状态,如果任务池已启动不允许再次启动
private volatile boolean satrtFlag = false;
//构造方法私有化,单例
private BusiExcuteTask(){}
public static BusiExcuteTask getInstance(){
return busiExcuteTask;
}
//设置任务
public void setTask(BusiTask busiTask){
queue.add(busiTask);
}
//执行
public void receiveTask(){
//只有一个线程来执行任务
final ReentrantLock lock = this.lock;
lock.lock();
try{
if(!satrtFlag) {
satrtFlag = true;//正在执行状态
new Thread(busiExcuteTask).start();//异步开启延时队列任务池
}
} finally {
lock.unlock();
}
}
@Override
public void run() {
excuteTask();
}
public void excuteTask(){
try{
//队列有任务就执行,没有任务就停止线程
while (queue.size() > 0){
try {
System.out.println(Thread.currentThread().getName() + "开始消费");
BusiTask task = queue.take();
new Thread(task).start();//异步
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
satrtFlag = false;//停止标志
}
}
public static void main(String[] args) {
for(int i = 0;i<10;i++){
final int j = i;
new Thread(new Runnable() {
@Override
public void run() {
BusiExcuteTask busiExcuteTask = BusiExcuteTask.getInstance();
int dt = new Random().nextInt(10) + 1;
System.out.println(Thread.currentThread().getName() + "任务" + j + "编号延迟" + dt + "s|" + new Date());
BusiTask busiTask = new BusiTask(j + "", "任务" + j, dt){
@Override
public void excute() {
System.out.println(Thread.currentThread().getName() + "任务" +"编号为" + getTaskId() + "的" + getTaskName() + "开始执行" + new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "编号为" + getTaskId() + "的" + getTaskName() + "结束执行" + new Date());
}
};
busiExcuteTask.setTask(busiTask);
//异步执行
busiExcuteTask.receiveTask();
}
}).start();
System.out.println(Thread.currentThread().getName() + "end");
}
}
}
解释
1.随时可以在不同的线程中定义延时任务,放到任务池里开始倒计时准备执行。
2.任务池的核心就是一个延时队列DelayQueue。
3.任务池如果有任务,就会起一个新的线程来从延时队列不断获取倒计时结束的任务开始执行,如果没有任务了就会停止任务池的线程。
4.新的任务就是不断的往延时队列中放任务,只要有任务就需要启动任务池的线程。
5.任务池中的任务,每个都是单独线程单独执行的。
6.所有的线程可以优化成用线程池统一管理。