1.java实现延迟消息(队列DelayQueue)
DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
缓存系统的设计:这里使用DelayQueue保存缓存元素的有效期,一个线程(生产者)设置失效实现循环添加消息,使用一个线程(消费者)循环查询
DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了
应用场景:
- 消息生产和消费有时间窗口要求,例如在电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条延时消息。这条消息将会在 30 分钟以后投递给消费者,消费者收到此消息后需要判断对应的订单是否已完成支付。如支付未完成,则关闭订单。如已完成支付则忽略。
- 通过消息触发一些定时任务,例如在某一固定时间点向用户发送提醒消息。
前提条件:放置在DelayQueue的元素需要实现Delayed接口,Delayed接口使对象成为延迟对象,它使存放在DelayQueue类中的对象具有了激活日期
CompareTo(Delayed o):Delayed接口继承了Comparable接口,因此有了这个方法。
getDelay(TimeUnit unit):这个方法返回到激活日期的剩余时间,时间单位由单位参数指定。
2.实现Delayed接口
package com.violet.Queue;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class MessageData implements Delayed {
private static final AtomicLong atomic = new AtomicLong(0);
// 数据的失效时间点
private final long time;
// 序号
private final long seqno;
/**
* @param deadline 数据失效时间点
*/
public MessageData(long deadline) {
this.time = deadline;
//序号自增类似于i++ ,这里使用AtomicLong实现原子操作
this.seqno = atomic.getAndIncrement();
}
/**
* 返回剩余有效时间
*
* @param unit 时间单位
*/
@Override
public long getDelay(TimeUnit unit) {
//查看是不是当这个时间到期时,消息被消费
System.out.println(unit.convert(this.time - System.currentTimeMillis(), TimeUnit.NANOSECONDS));
return unit.convert(this.time - System.currentTimeMillis(), TimeUnit.NANOSECONDS);
}
/**
* 比较两个Delayed对象的大小, 比较顺序如下:
* 1. 如果是对象本身, 返回0;
* 2. 比较失效时间点, 先失效的返回-1,后失效的返回1;
* 3. 比较元素序号, 序号小的返回-1, 否则返回1.
* 4. 非Data类型元素, 比较剩余有效时间, 剩余有效时间小的返回-1,大的返回1,相同返回0
*/
@Override
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof MessageData) {
MessageData x = (MessageData) other;
// 优先比较失效时间
long diff = this.time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (this.seqno < x.seqno) // 剩余时间相同则比较序号
return -1;
else
return 1;
}
// 一般不会执行到此处,除非元素不是MessageData类型
long diff = this.getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
@Override
public String toString() {
return "Data{" +
"time=" + time +
", seqno=" + seqno +
"}, isValid=" + isValid();
}
private boolean isValid() {
return this.getDelay(TimeUnit.NANOSECONDS) > 0;
}
}
3.生产者
package com.violet.Queue;
import java.util.Date;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ThreadLocalRandom;
public class Producer implements Runnable {
private final DelayQueue<MessageData> queue;
public Producer(DelayQueue<MessageData> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
//选取系统当前时间+随机生成的时间 来设置消息失效时间
long currentTime = System.currentTimeMillis();
long validTime = ThreadLocalRandom.current().nextLong(1000L, 7000L);
MessageData data = new MessageData(currentTime + validTime);
queue.put(data);
System.out.println(Thread.currentThread().getName() + ": put " + data);
try {
//为了效果显著这里将线程停的时间长一点
Thread.sleep(1000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.消费者
package com.violet.Queue;
import java.util.concurrent.DelayQueue;
public class Consumer implements Runnable {
private final DelayQueue<MessageData> queue;
public Consumer(DelayQueue<MessageData> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
MessageData data = queue.take();
System.out.println(Thread.currentThread().getName() + ": take " + data);
Thread.yield();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5.main方法
package com.violet.Queue;
import java.util.concurrent.DelayQueue;
public class Main {
public static void main(String[] args) {
DelayQueue<MessageData> queue = new DelayQueue<>();
Thread c1 = new Thread(new Consumer(queue), "consumer");
Thread p1 = new Thread(new Producer(queue), "producer");
c1.start();
p1.start();
}
}
6.效果