一、业务场景。
用户申请一笔订单,多加公司参与报价,当在用户设置的报价时间内未报价的公司订单自动取消。
二、实现方法
采用java DelayQueue无边界消息队列
Delayed,一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序。
简单的延时队列要有三部分:第一实现了Delayed接口的消息体、第二消费消息的消费者、第三存放消息的延时队列
Demo
消息体
import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; /** * @Auther: jinpeng * @Date: 2019/3/14 8:40 * @Description: */ public class Message implements Delayed { private String id; private String body; private long excuteTime; public String getId() { return id; } public String getBody() { return body; } public long getExcuteTime() { return excuteTime; } public Message(String id, String body, long delayTime) { this.id = id; this.body = body; this.excuteTime = TimeUnit.NANOSECONDS.convert(delayTime, TimeUnit.MILLISECONDS) + System.nanoTime(); } // 自定义 因为实现了compare接口 @Override public int compareTo(Delayed delayed) { Message msg = (Message) delayed; return Integer.valueOf(this.id) > Integer.valueOf(msg.id) ? 1 : (Integer.valueOf(this.id) < Integer.valueOf(msg.id) ? -1 : 0); } // 延迟任务是否到时就是按照这个方法判断如果返回的是负数则说明到期否则还没到期 @Override public long getDelay(TimeUnit unit) { return unit.convert(this.excuteTime - System.nanoTime(), TimeUnit.NANOSECONDS); } } 延时队列 import java.util.concurrent.DelayQueue; /** * @Auther: jinpeng * @Date: 2019/3/14 8:43 * @Description: */ public class Consumer implements Runnable { // 延时队列 ,消费者从其中获取消息进行消费 private DelayQueuequeue; public Consumer(DelayQueue queue) { this.queue = queue; } @Override public void run() { while (true) { try { Message take = queue.take(); String applyOrderId = take.getBody(); System.out.println(applyOrderId + "线程号===" + take.getId()); // } catch (InterruptedException e) { e.printStackTrace(); } } } }
启动延时队列
import java.util.concurrent.DelayQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Auther: jinpeng * @Date: 2019/3/14 9:36 * @Description: */ public class TestMain { public static void main(String[] args) { DelayQueueq1 = new DelayQueue (); DelayQueue q2 = new DelayQueue (); DelayQueue q3 = new DelayQueue (); DelayQueue q4 = new DelayQueue (); DelayQueue q5 = new DelayQueue (); DelayQueue q6 = new DelayQueue (); // 添加延时消息,m1 延时3s Message m1 = new Message("thread3", "3", 3000); Message m2 = new Message("thread4", "4", 10000); Message m3 = new Message("thread5", "5", 5000); Message m4 = new Message("thread6", "6", 6000); Message m6 = new Message("thread7", "7", 20000); Message m5 = new Message("thread4-1", "4", 10000); q1.offer(m1); q2.offer(m2); q3.offer(m3); q4.offer(m4); q5.offer(m5); q6.offer(m6); ExecutorService exec1 = Executors.newFixedThreadPool(1); exec1.execute(new Consumer(q1)); exec1.shutdown(); ExecutorService exec2 = Executors.newFixedThreadPool(1); exec2.execute(new Consumer(q2)); exec2.shutdown(); ExecutorService exec3 = Executors.newFixedThreadPool(1); exec3.execute(new Consumer(q3)); exec3.shutdown(); ExecutorService exec4 = Executors.newFixedThreadPool(1); exec4.execute(new Consumer(q4)); exec4.shutdown(); ExecutorService exec5 = Executors.newFixedThreadPool(1); exec5.execute(new Consumer(q5)); exec5.shutdown(); ExecutorService exec6 = Executors.newFixedThreadPool(1); exec6.execute(new Consumer(q6)); exec6.shutdown(); }
知其然知其所以然:
DelayQueue中内部使用的是PriorityQueue存放数据,使用ReentrantLock实现线程同步,可知是阻塞队列。另外队列里面的元素要实现Delayed接口,一个是获取当前剩余时间的接口,一个是元素比较的接口,因为这个是有优先级的队列。
插入元素到队列,主要插入元素要实现Delayed接口。
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {(2)
leader = null;
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
首先获取独占锁,然后添加元素到优先级队列,由于q是优先级队列,所以添加元素后,peek并不一定是当前添加的元素,如果(2)为true,说明当前元素e的优先级最小也就即将过期的,这时候激活avaliable变量条件队列里面的线程,通知他们队列里面有元素了。
获取并移除队列首元素,如果队列没有过期元素则等待。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
//获取但不移除队首元素(1)
E first = q.peek();
if (first == null)
available.await();//(2)
else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay <= 0)//(3)
return q.poll();
else if (leader != null)//(4)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;//(5)
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)//(6)
available.signal();
lock.unlock();
}
}
第一次调用take时候由于队列空,所以调用(2)把当前线程放入available的条件队列等待,当执行offer并且添加的元素就是队首元素时候就会通知最先等待的线程激活,循环重新获取队首元素,这时候first假如不空,则调用getdelay方法看该元素海剩下多少时间就过期了,如果delay<=0则说明已经过期,则直接出队返回。否者看leader是否为null,不为null则说明是其他线程也在执行take则把该线程放入条件队列,否者是当前线程执行的take方法,则调用(5)await直到剩余过期时间到(这期间该线程会释放锁,所以其他线程可以offer添加元素,也可以take阻塞自己),剩余过期时间到后,该线程会重新竞争得到锁,重新进入循环。
(6)说明当前take返回了元素,如果当前队列还有元素则调用singal激活条件队列里面可能有的等待线程。leader那么为null,那么是第一次调用take获取过期元素的线程,第一次调用的线程调用设置等待时间的await方法等待数据过期,后面调用take的线程则调用await直到signal。
获取并移除队头过期元素,否者返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
//如果队列为空,或者不为空但是队头元素没有过期则返回null
if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
return null;
else
return q.poll();
} finally {
lock.unlock();
}
}
原理分析来自https://www.jianshu.com/p/2659eb72134b
后续更新自动派单逻辑及原理
没有因为场景的技术学习都还给了各大博主