DelayQueue是一个无界阻塞队列,只有消息到期才能从中获取到消息。话不多说,实现一个Demo。
消息实体:
public class Message implements Delayed {
private String body;
private long delayTime;
private long putTime;
/**
* 用于返回剩余时间
* 消息是否到期则是通过此方法判断
* 返回小于等于0则到期
*/
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(putTime + delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
/**
* 因为Delayed继承于Comparable
* 所以需要实现compareTo方法,用于排序
* 该对象(this)小于、等于或大于指定对象(o),则分别返回负整数、零或正整数。
*/
@Override
public int compareTo(Delayed o) {
long result = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
if (result < 0) {
return -1;
} else if (result > 0) {
return 1;
} else {
return 0;
}
}
// getter/setter
public Message(String body, long delayTime) {
this.body = body;
this.delayTime = delayTime;
this.putTime = new Date().getTime();
}
}
消费者线程:
public class Consumer implements Runnable {
// 延时队列
private DelayQueue queue;
public Consumer(DelayQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Message message = queue.take();
System.out.println("接收到消息内容:" + message.getBody());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// getter/setter
}
生产者:
public class Producer {
public static void main(String[] args) {
DelayQueue queue = new DelayQueue<>();
// 延时5秒
Message m1 = new Message("兄dei吃饭了吗?", 5000);
// 延时15秒
Message m2 = new Message("开黑吗???我玩亚索!", 15000);
// 将延时消息放到延时队列
queue.add(m1);
queue.offer(m2);
new Thread(new Consumer(queue)).start();
}
}
成功运行,等待5秒,15秒,输出消息。
要点:
1.add方法跟offer方法其实是一样的
2.需要始终保证最快到期的消息在队首(关键在于消息实体的compareTo方法),否则会导致到期消息无法及时消费
查看DelayQueue.take()源码可以发现,它会获取队首消息并判断是否到期,队首消息到期才返回结果;
队首消息没到期:
如果有其他线程在等待(leader != null),则阻塞当前线程
如果当前没有其他线程等待(leader == null),则阻塞当前线程直到延迟时间
使用redis提供的有序数据结构zset,把过期时间戳作为score。
当然已经有现成的轮子了------Redisson的RDelayedQueue + RBlockingDeque
原理就是使用redis的zset + list,先来个Demo。
消息实体:
public class Order {
private String createdTime;
public Order() {
this.createdTime = new SimpleDateFormat("hh:mm:ss").format(new Date());
}
public String getCreatedTime() {
return createdTime;
}
}
生产者:
public class Producer {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.3.183:6379");
RedissonClient redissonClient = Redisson.create(config);
RBlockingDeque blockingDeque = redissonClient.getBlockingDeque("delay_queue");
RDelayedQueue delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Order order = new Order();
delayedQueue.offer(order, 1, TimeUnit.SECONDS);
System.out.println("成功发送延时队列");
}
delayedQueue.destroy();
}
}
消费者:
public class Consumer {
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.3.183:6379");
RedissonClient redissonClient = Redisson.create(config);
RBlockingDeque blockingDeque = redissonClient.getBlockingDeque("delay_queue");
while (true) {
Order order = null;
try {
// 如果没有到期消息,返回null
order = blockingDeque.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (order != null)
System.out.println("订单取消时间:" + new SimpleDateFormat("hh:mm:ss").format(new Date()) + "==订单生成时间" + order.getCreatedTime());
else
continue;
}
}
}
分别执行生产者/消费者。
Redisson底层其实就是在执行Lua脚本,源码解析参考
https://www.jianshu.com/p/8fa478da3b00
以上代码仓库
https://gitee.com/Deep_feel/study_notes/tree/master/test/src/main/java/delayQueue