java实现消息队列以及延迟消息(队列DelayQueue)

时间:2021-2-27 作者:admin

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.效果

java实现消息队列以及延迟消息(队列DelayQueue)

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。