DelayQueue 简介
由优先级堆支持的、基于时间的调度队列,内部基于无界队列PriorityQueue实现,而无界队列基于数组的扩容实现。
队列创建
BlockingQueue<String> blockingQueue = new DelayQueue();
要求
入队的对象必须要实现Delayed接口,而Delayed集成自Comparable接口
应用场景
对缓存超时的数据进行移除
- 当向缓存中添加key-value对时,如果这个key在缓存中存在并且还没有过期,需要用这个key对应的新过期时间。
- 为了能够让DelayQueue将其已保存的key删除,需要重写实现Delayed接口添加到DelayQueue的DelayedItem的hashCode函数和equals函数。
- 当缓存关闭,监控程序也应关闭,因而监控线程应当用守护线程
代码如下:
public class Cache<K,V> {
public ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>();
public DelayQueue<DelayedItem<K>> queue = new DelayQueue<>();
public void put(K k , V v,long liveTime){
V v2 = map.put(k,v);
DelayedItem<K> tmpItem = new DelayedItem<K>(k,liveTime);
if (v2 != null) {
queue.remove(tmpItem);
}
queue.put(tmpItem);
}
public Cache(){
Thread t = new Thread(){
@Override
public void run(){
dameonCheckOverdueKey();
}
};
t.setDaemon(true);
t.start();
}
public void dameonCheckOverdueKey(){
while (true) {
DelayedItem<K> delayedItem = queue.poll();
if (delayedItem != null) {
map.remove(delayedItem.getT());
System.out.println(System.nanoTime()+" remove "+delayedItem.getT() +" from cache");
}
try {
Thread.sleep(300);
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws InterruptedException {
Random random = new Random();
int cacheNumber = 10;
int liveTime = 0;
Cache<String, Integer> cache = new Cache<String, Integer>();
for (int i = 0; i < cacheNumber; i++) {
liveTime = random.nextInt(3000);
System.out.println(i+" "+liveTime);
cache.put(i+"", i, random.nextInt(liveTime));
if (random.nextInt(cacheNumber) > 7) {
liveTime = random.nextInt(3000);
System.out.println(i+" "+liveTime);
cache.put(i+"", i, random.nextInt(liveTime));
}
}
Thread.sleep(3000);
System.out.println();
}
}
class DelayedItem<T> implements Delayed{
private T t;
private long liveTime;
private long removeTime;
public DelayedItem( T t, long liveTime ) {
this.t = t;
this.liveTime = liveTime;
this.removeTime = TimeUnit.NANOSECONDS.convert(liveTime,TimeUnit.NANOSECONDS) + System.nanoTime();
}
@Override
public long getDelay( TimeUnit unit ) {
return unit.convert(removeTime - System.nanoTime(),unit);
}
@Override
public int compareTo( Delayed o ) {
if (o == null) return 1;
if (o == this) return 0;
if (o instanceof DelayedItem){
DelayedItem<T> tmpDelayedItem = (DelayedItem<T>)o;
if (liveTime > tmpDelayedItem.liveTime ) {
return 1;
}else if (liveTime == tmpDelayedItem.liveTime) {
return 0;
}else {
return -1;
}
}
long diff = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return diff > 0 ? 1:diff == 0? 0:-1;
}
@Override
public int hashCode(){
return t.hashCode();
}
@Override
public boolean equals(Object object){
if (object instanceof DelayedItem) {
return object.hashCode() == hashCode() ?true:false;
}
return false;
}
public T getT( ) {
return t;
}
public void setT( T t ) {
this.t = t;
}
}
任务超时处理
比如 在京东商城,买了一件东西,提交了订单,但是还没付款,30分钟后如果没有付款,则会自动取消
实现方式如下:
- 使用数据库定时任务,每隔几秒扫描订单表,找出超时订单后关闭
- 使用spring的@Scheduled注解启动定时任务或者使用Quartz任务管理器,定时触发任务,处理超时订单。
- 使用消息中间件,Active或者RocketMQ提供了延迟消息队列,下单后往延迟消息队列中发消息,超时后,消费端会接收到一条延迟的 订单消息,并做相应处理
- 使用DelayQueue来实现
下面我们介绍使用DelayQueue 来怎么实现?
@Component
@Lazy(false)
public class DelayOrderComponent{
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderService orderService;
private static DelayQueue<OrderMessage> delayQueue = new DelayQueue<OrderMessage>();
@PostConstruct
public void init() throws Exception {
/**初始化时加载数据库中需处理超时的订单**/
List<OverTimeOrder> orderList = orderMapper.selectOverTimeOrder();
for (int i = 0; i < orderList.size(); i++) {
OrderMessage orderMessage = new OrderMessage(propertyCarList.get(i).getOrderId(),propertyCarList.get(i).getCreateTime());
this.addToOrderDelayQueue(orderMessage);
}
/**启动一个线程,去取延迟消息**/
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
OrderMessage message = null;
while (true) {
try {
message = delayQueue.take();
//处理超时订单
orderService.closeOverTimeOrder(message.getOrderId());
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
/**加入延迟消息队列**/
private boolean addToOrderDelayQueue(OrderMessage orderMessage){
return delayQueue.add(orderMessage);
}
/**从延迟队列中移除**/
private void removeToOrderDelayQueue(OrderMessage orderMessage){
if(Tools.isEmpty(orderMessage)){
return;
}
for (Iterator<OrderMessage> iterator = delayQueue.iterator(); iterator.hasNext();) {
OrderMessage queue = (OrderMessage) iterator.next();
if(orderMessage.getOrderId().equals(queue.getOrderId())){
delayQueue.remove(queue);
}
}
}
}
(1) init()方法:在对象实例化的时候,加载数据库中的需要延时处理的订单,并启动一个线程等待处理超时出队列的订单。
(2) 对外提供加入消息到延迟队列方法 addToOrderDelayQueue ,当下单的时候加入延迟消息队列。
(3) 对外提供删除延迟消息方法 removeToOrderDelayQueue ,当用户主动取消订单,或者支付成功后使用。
public class OrderMessage implements Delayed {
private final static long DELAY = 15*60*1000L;//默认延迟15分钟
private final String orderId;//订单号
private final long startTime ;//开始时间
private final long expire ;//到期时间
private final Date now; //创建时间
private final String orderMsg;//订单其他信息JSON方式保存,备用字段
public OrderMessage(String orderId, String startTimeStr14 ,long secondsDelay) {
super();
this.orderId = orderId;
this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
this.expire = startTime + (secondsDelay*1000);
this.now = new Date();
this.orderMsg="";
}
public OrderMessage(String orderId, String startTimeStr14, String orderMsg ,long secondsDelay) {
super();
this.orderId = orderId;
this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
this.expire = startTime + (secondsDelay*1000);
this.orderMsg = orderMsg;
this.now = new Date();
}
public OrderMessage(String orderId, String startTimeStr14) {
super();
this.orderId = orderId;
this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
this.expire = startTime + DELAY;
this.now = new Date();
this.orderMsg="";
}
public OrderMessage(String orderId, String startTimeStr14, String orderMsg) {
super();
this.orderId = orderId;
this.startTime = DateUtils.toDateTime14(startTimeStr14).getTime();
this.expire = startTime + DELAY;
this.orderMsg = orderMsg;
this.now = new Date();
}
@Override
public int compareTo(Delayed o) {
return (int) (this.getDelay(TimeUnit.MILLISECONDS) -o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(this.expire - System.currentTimeMillis() , TimeUnit.MILLISECONDS);
}
public String getOrderId() {
return orderId;
}
public String getOrderMsg() {
return orderMsg;
}
public Date getNow() {
return now;
}
public long getStartTime() {
return startTime;
}
public long getExpire() {
return expire;
}
}
1、 延迟消息必须实现 Delayed 接口,并实现compareTo和getDelay接口。
2、此处我定义了多个构造方法,方便在多种场景下使用。
public static void main(String[] args) {
final DelayQueue<OrderMessage> delayQueue = new DelayQueue<OrderMessage>();
delayQueue.add(new OrderMessage(""+1, DateUtils.curDateTimeStr14(),"3秒后执行"));
delayQueue.add(new OrderMessage(""+2, DateUtils.curDateTimeStr14(),"4秒后执行"));
delayQueue.add(new OrderMessage(""+3, DateUtils.curDateTimeStr14(),"8秒后执行"));
/**启动一个线程,处理延迟消息**/
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
OrderMessage message = null;
while (true) {
try {
message = delayQueue.take();
System.out.println(new Date()+" 处理延迟消息: "+JSON.toJSONString(message));
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
源码分析
首先看一下DlayedQueue源码:
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
implements BlockingQueue<E> {
private final transient ReentrantLock lock = new ReentrantLock();
private final PriorityQueue<E> q = new PriorityQueue<E>();
// ...
}
这是一小部分源码,从这段源码可以看出,DelayedQueue中处理的是实现Delayed接口的任务,DelayedQueue使用lock来实现线程同步,使用PriorityQueue来管理任务。那么Delayed接口是什么呢?
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
这是Delayed接口的官方注释,意思是:一个混合风格的接口,为创建给定延迟的任务。(翻译的不太好,请纠正);其中有两个重要的方法compareTo和getDelay,第一个是比较两个任务的延迟时间进行排序,第二个方法用来获取延迟时间。
priorityQueue是一种优先级队列,这里优先级就是延迟时间,也就是说进入队列的任务安装优先级进行排序,延迟时间最短的在队列前面,先被处理,也就是说,每次从队列中取出的任务都将是到期的任务。比如我们实现一个缓存,当某个key-value对超期了,我们就可以从队列前取出,然后进行销毁操作。