阅读本文,请先了解PriorityQueue的使用(【Java】PriorityQueue 源码分析 ),以及AQS等
一、DelayQueue介绍
一个无限制的blocking queue的Delayed
元素,其中元素只能在其延迟到期时才被使用。 队列的头是Delayed
元素,其延迟期满后保存时间。 如果没有延迟到期,那么没有头, poll
会返回null
。 当元素的getDelay(TimeUnit.NANOSECONDS)
方法返回小于或等于零的值时,就会发生getDelay(TimeUnit.NANOSECONDS)
。 即使未使用的元素不能使用take
或poll
,它们另外被视为普通元素。 例如, size
方法返回到期和未到期元素的计数。 此队列不允许空元素。
二、属性
1 // 全局锁 2 private final transient ReentrantLock lock = new ReentrantLock(); 3 4 // 优先级队列 5 private final PriorityQueue<E> q = new PriorityQueue<E>(); 6 7 // 最先消费线程 8 private Thread leader = null; 9 10 // 可用条件 11 private final Condition available = lock.newCondition();
三、方法
1、构造方法
1 // 创建一个空的阻塞式延迟队列 2 public DelayQueue() {} 3 4 // 根据集合创建一个阻塞式延迟队列 5 public DelayQueue(Collection<? extends E> c) { 6 this.addAll(c); 7 }
2、offer() 方法
1 // 放入元素到队列中 2 public boolean offer(E e) { 3 final ReentrantLock lock = this.lock; 4 // 获取锁 5 lock.lock(); 6 try { 7 // 调用数据优先级队列的 offer() 方法放入队列中 8 q.offer(e); 9 // 查看放入元素 与 优先级队列顶元素是否相同 10 if (q.peek() == e) { 11 leader = null; 12 // 通知其他线程,队列可用 13 available.signal(); 14 } 15 return true; 16 } finally { 17 // 释放锁 18 lock.unlock(); 19 } 20 }
3、peek() 方法
1 // 获取但不取出,优先级队列中队列顶的元素 2 public E peek() { 3 final ReentrantLock lock = this.lock; 4 lock.lock(); 5 try { 6 7 return q.peek(); 8 } finally { 9 lock.unlock(); 10 } 11 }
3、take() 方法
1 // 获取队列元素 2 public E take() throws InterruptedException { 3 final ReentrantLock lock = this.lock; 4 // 获取可以被打断的锁 5 lock.lockInterruptibly(); 6 try { 7 for (;;) { 8 // 查看队列头的元素 9 E first = q.peek(); 10 if (first == null) 11 // 队列无用元素,线程等待可用 12 available.await(); 13 else { 14 // 延迟时间 15 long delay = first.getDelay(NANOSECONDS); 16 // 不为空但是队头元素有过期 17 if (delay <= 0) 18 // 取出队列顶的元素,返回 19 return q.poll(); 20 // 没有过期元素时 21 first = null; // don't retain ref while waiting 22 // 前面还有消费线程等待消费 23 if (leader != null) 24 available.await(); 25 else { 26 // 设置本线程为最先消费线程 27 Thread thisThread = Thread.currentThread(); 28 leader = thisThread; 29 try { 30 // 本线程等待 delay 时长,后被唤醒 31 // delay 时长 后,队列头第一个元素会过期 32 available.awaitNanos(delay); 33 } finally { 34 if (leader == thisThread) 35 leader = null; 36 } 37 } 38 } 39 } 40 } finally { 41 // 如果 leader 为空,且队列不为空,唤醒一个消费线程 42 if (leader == null && q.peek() != null) 43 44 available.signal(); 45 // 释放锁 46 lock.unlock(); 47 } 48 }
四、运行流程
示例代码:
1 public class TestDelayQueue { 2 3 4 public static void main(String[] args) throws InterruptedException { 5 6 DelayQueue<DelayedEle> queue = new DelayQueue(); 7 8 for (int i = 0; i < 5; i++) { 9 int finalI = i; 10 new Thread(() -> { 11 queue.offer(new DelayedEle((5-finalI) * 1000, "" + finalI)); 12 System.out.println("生产:" + finalI); 13 }).start(); 14 } 15 16 Thread.sleep(2000); 17 18 for (int j = 0; j < 5; j++) { 19 int finalI = j; 20 new Thread(() -> { 21 try { 22 System.out.println("消费:" + queue.take()); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 }).start(); 27 } 28 } 29 } 30 31 class DelayedEle implements Delayed { 32 33 private final long delayTime; //延迟时间 34 private final long expire; //到期时间 35 private String data; //数据 36 37 public DelayedEle(long delay, String data) { 38 delayTime = delay; 39 this.data = data; 40 expire = System.currentTimeMillis() + delay; 41 } 42 43 /** 44 * 剩余时间=到期时间-当前时间 45 */ 46 @Override 47 public long getDelay(TimeUnit unit) { 48 return unit.convert(this.expire - System.currentTimeMillis() , TimeUnit.MILLISECONDS); 49 } 50 51 /** 52 * 优先队列里面优先级规则 53 */ 54 @Override 55 public int compareTo(Delayed o) { 56 return (int) (this.getDelay(TimeUnit.MILLISECONDS) -o.getDelay(TimeUnit.MILLISECONDS)); 57 } 58 59 @Override 60 public String toString() { 61 final StringBuilder sb = new StringBuilder("DelayedElement{"); 62 sb.append("delay=").append(delayTime); 63 sb.append(", expire=").append(expire); 64 sb.append(", data='").append(data).append('\''); 65 sb.append('}'); 66 return sb.toString(); 67 } 68 }
五、总结
1、DelayQueue 底层使用了PriorityQueue来存储数据,存放的数据要求实现Delayed接口
2、DelayQueue 是一个无界的阻塞队列。
3、DelayQueue 队列中的元素只有延迟期满,才能从队列中被出去,