什么是队列
队列(queue) 是一种特殊类型的抽象数据类型或集合(可以用链表实现,也可以用数组实现)。集合中的实体对象按顺序保存,可以通过在序列的一端添加实体和从序列的另一端移除实体来进行修改。
将元素添加到队列后的操作称为入队,从队列中移除元素的操作称为出队。也允许其他的一些操作,包括 peek、element等。另外队列还分为 单端队列(queue) 和 双端队列(deque) 。
队列遵循的是先进先出的原则
队列既可以是数组实现也可以是链表实现。所以当我们在 Java 中使用队列的时候,Deque 的实现类就是;LinkedList 和 ArrayDeque的实现类。
队列不只是单端从一个口入另外一个口出,也可以是双端队列。例如在 Java 中 Queue 是单端队列接口、Deque 是双端队列接口,都有对应的实现类。
我们将一一种特殊的队列让友友们更好的理解队列 那就是延迟队列
什么是延迟队列?
DelayQueue 是一个 BlockingQueue(无界阻塞)队列,它封装了一个使用完全二叉堆排序元素的 PriorityQueue(优先队列)。在添加元素时使用 Delay(延迟时间)作为排序条件,延迟最小的元素会优先放到队首。
以下为实战部分
延迟队列的实现,主要为在优先队列的基础上,添加可重入锁 ReentrantLock 对阻塞队列的实现。当数据存放时,按照二叉堆结构排序元素,出队时依照排序结构进行迁移。
延迟队列的使用,是以在 DelayQueue 中存放实现了 Delayed 延迟接口的对象。因为只有实现这个对象,才能比较出当前元素与所需存放到对应位置的一个比对计算过程。
另外这里的核心点包括:PriorityQueue —— 优先队列、ReentrantLock —— 可重入锁、Condition —— 信号量
入队的实现
在存放元素时,以遵循它的特点,会在存存放过程中,通过队尾元素向上比对迁移。
DelayQueue 延迟队列,元素入队最终会调用到优先队列的 PriorityQueue#siftUpComparable 方法。
以入队元素2举例,如图所示入队过程。
(k - 1) >>> 1 为什么使用 >>> 右移1位;
首先我们是需要通过右移替代除以2的运算,提升运算效率,找到父节点。移位器比除法器简单得多,在大多数处理器上,移位指令的执行速度比除法指令快
是算术位移,>>> 是逻辑右移
算术和逻辑左移和乘法的等价,但由于符号位的存在算术右移和除法不等价
首先将元素2挂到队列尾部,之后通过 (k - 1) >>> 1 计算父节点位置,与对应元素进行比对和判断交换。
交换过程包括2->6、2->5,以此交换结束后元素保存完毕。
Queue<Job> queue = new DelayQueue<>();
queue.add(new Job("1号", 1000L));
queue.add(new Job("3号", 3000L));
queue.add(new Job("5号", 5000L));
queue.add(new Job("11号", 11000L));
queue.add(new Job("4号", 4000L));
queue.add(new Job("6号", 6000L));
queue.add(new Job("7号", 7000L));
queue.add(new Job("12号", 12000L));
queue.add(new Job("15号", 15000L));
queue.add(new Job("10号", 10000L));
queue.add(new Job("9号", 9000L));
queue.add(new Job("8号", 8000L));
// 新增入队
queue.add(new Job("2号", 2000L));
while (true) {
Job poll = queue.poll();
if (null == poll) {
Thread.sleep(10);
continue;
}
logger.info(poll.getName());
}
2:出队实现
元素的出队其实很简单,只要把根元素直接删除弹出即可。但剩余接下里的步骤才是复杂的,因为需要在根元素迁移走后,寻找另外的最小元素迁移到对头。这个过程与入队正好相反,这是一个不断向下迁移的过程。
4:操作加锁
在延迟队列关于元素的操作中,都会进行加锁处理。
offer:——入队元素
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
q.offer(e);
if (q.peek() == e) {
available.signal();
}
return true;
} finally {
lock.unlock();
}
}
poll:——出队元素
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
if (first == null || first.getDelay(NANOSECONDS) > 0) {
return null;
} else {
return q.poll();
}
} finally {
lock.unlock();
}
}
元素的入队和出队都会使用 ReentrantLock 的方式进行加锁处理。确保线程安全
最后附上经典面试题。
1:单端队列和双端队列,分别对应的实现类是哪个?
2:简述延迟队列/优先队列的实现方式?
3:二叉堆插入/弹出元素的过程?
4:延迟队列的使用场景?
5:延迟队列为什么添加信号量?
友友们在评论区写下你们的答案!
以上的是线性数据结构-手写队列-Queue 若需完整代码 可识别二维码后 给您发代码。