DelayQueue是基于java中一个非常牛逼的队列PriorityQueue(优先队列),PriorityQueue是java1.5新加入的,当我看到Doug Lea大神的署名之后,我就知道这个队列不简单,那我们先来看一下他的源码吧:

作为一个队列来说,最基础的就是新增和查询,首先我们看下入队的逻辑:

1.入队

PriorityQueue提供了offer方法新增元素(add方法其实也是offer实现的),我们直接看下源码:

public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

offer方法首先判断是否需要扩容,若需要则走grow方法:

private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}

当长度小于64,扩容一倍+2,否则扩容50%。

再往下看若队列中没有元素,直接复制下标为0的元素,否则调用siftUp方法:

private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}

俩方法差不多一个,具体可搜compare和compareTo的区别如:https://blog.csdn.net/fly910905/article/details/81670353,我们直接看siftUpComparable方法:

private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}

     

结合上图和代码可以看出每个节点新增时,首先会根据节点下标计算出当前新节点应该属于的节点的父节点,比较当小于父节点则交换,无限循环,知道不存在父节点或者当前节点大于父节点的值,这样可以保证每个节点都比起子节点要小。

2.出队

入队的时候基本都差不多,但出队却有好几种,我们首先看peek方法:

public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return q.peek();
} finally {
lock.unlock();
}
}
public E peek() {
return (size == 0) ? null : (E) queue[0];
}

代码简洁明了,就是查询出第一个,这只能算查询,算不上出队,我觉得应该叫点名。

再看poll方法:

public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek(); //取第一个节点
if (first == null || first.getDelay(NANOSECONDS) > 0) //节点为空或者首节点未到延时时间直接返回null
return null;
else
return q.poll(); //PriorityQueue取节点逻辑
} finally {
lock.unlock();
}
}

再看PriorityQueue.poll方法:

public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
return result;
}

首先取出第一个节点,然后将最后一个节点放替换首节点,并与子节点对比找出最小的并替换直到当前节点为最小为止,具体替换流程见siftDown代码:

private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least 拿到左子节点下标
Object c = queue[child];
int right = child + 1; //右子节点下标
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right]; //取出左右节点较小的
if (key.compareTo((E) c) <= 0) //当前节点比子节点小,结束流程
break;
queue[k] = c; //替换子节点至父节点
k = child;
}
queue[k] = key;
}

这个代码看起来稍微复杂点,会首先拿到左子节点和右子节点,对比取出较小的节点后与当前节点对比,将小的放在父节点位置,其实这里也是保证替换后的节点依然保持每个父节点最小,符合小顶堆。具体流程如下图所示:

我们最后看下take方法的实现:

public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek(); //取出第一个节点
if (first == null) //首节点为空说明队列为空,await等待
available.await();
else { //说明队列中有节点
long delay = first.getDelay(NANOSECONDS); //获取首节点延时时间
if (delay <= 0) //延时时间到期,直接取
return q.poll();
first = null; // don't retain ref while waiting
if (leader != null) //说明当前有其他线程在操作(一般是其他线程在await)
available.await();
else { //这里设置操作线程为自己,并等待延时时间时长
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && q.peek() != null)
available.signal();
lock.unlock();
}
}

这个实现一看就是阻塞式等待,取不到不罢休系列。

总结:

  这篇写的还是比较简单的,大体介绍了DelayQueue的实现,也从底层了解了小顶堆PriorityQueue的实现,算是补充了之前对延时队列的具体实现,这篇主要是通过一个小顶堆的实现,保证每次取得值都是最小的,而又不用像数组那样每次插入都要重新排序,这里只要排序一部分就可以,也保证了性能,而DelayQueue中,加入了ReentrantLock保证了多线程的线程安全,同时加入Condition实现了延时阻塞式存取的机制,jdk的代码还是牛,这里其实就是我之前写锁的时候介绍的等待通知模式的一种实现,结合起来看还是有一些收获的。

java延迟队列DelayQueue及底层优先队列PriorityQueue实现原理源码详解的更多相关文章

  1. java延迟队列

    大多数用到定时执行的功能都是用任务调度来做的,单身当碰到类似订餐业务/购物等这种业务就不好处理了,比如购物的订单功能,在你的订单管理中有N个订单,当订单超过十分钟未支付的时候自动释放购物车中的商品,订 ...

  2. 延迟队列DelayQueue take() 源码分析

    延迟队列DelayQueue take() 源码分析 在工作中使用了延迟队列,对其内部的实现很好奇,于是就研究了一下其运行原理,在这里就介绍一下take()方法的源码 1 take()源码 如下所示 ...

  3. RocketMQ源码详解 | Broker篇 · 其三:CommitLog、索引、消费队列

    概述 上一章中,已经介绍了 Broker 的文件系统的各个层次与部分细节,本章将继续了解在逻辑存储层的三个文件 CommitLog.IndexFile.ConsumerQueue 的一些细节.文章最后 ...

  4. RocketMQ源码详解 | Broker篇 · 其四:事务消息、批量消息、延迟消息

    概述 在上文中,我们讨论了消费者对于消息拉取的实现,对于 RocketMQ 这个黑盒的心脏部分,我们顺着消息的发送流程已经将其剖析了大半部分.本章我们不妨乘胜追击,接着讨论各种不同的消息的原理与实现. ...

  5. Java日志管理:Logger.getLogger()和LogFactory.getLog()的区别(详解Log4j)

    Java日志管理:Logger.getLogger()和LogFactory.getLog()的区别(详解Log4j) 博客分类: Java综合   第一.Logger.getLogger()和Log ...

  6. “全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. “全栈2019”Java第一百零四章:匿名内部类与外部成员互访详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. “全栈2019”Java第九十七章:在方法中访问局部内部类成员详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  10. “全栈2019”Java第九十二章:外部类与内部类成员覆盖详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

随机推荐

  1. Flutter学习

    常用网址 免费下载 !<AliFlutter 体系化建设和实践> Flutter 开发文档 Flutter实战 Dart 编程语言概览 pub仓库 main函数使用了(=>)符号, ...

  2. 【Azure 环境】使用 az ad group create 时候遇见 Insufficient privileges to complete the operation

    问题描述 使用China Azure,通过Azure CLI 创建AAD组报错,提示权限不足 Insufficient privileges to complete the operation # 使 ...

  3. Java 类中属性的使用

    1 类中属性的使用: 2 * 属性(成员变量) 局部变量 3 * 1.相同点: 4 * 定义变量的个格式: 数据类型 变量名 = 变量值 5 * 先声明 后使用 6 * 变量都有其对应的作用域 7 * ...

  4. python 字典列表,元组列表 列表嵌套字典 列表嵌套元组 字典嵌套列表

    列表嵌套字典 l=[] for i in alist: kk = {} names.append(i.string) a_url.append(i.get('href')) kk['章节名']=i.s ...

  5. docker部署监控Prometheus+Grafana

    目录 一.Prometheus简介 二.Prometheus基本原理 三.Prometheus架构图 四.Prometheus特性 五.Prometheus组件 六.Prometheus服务发现 七. ...

  6. 摆脱鼠标系列 - vscode - ctrl+up 光标上移动4行 ctrl+down 光标下移4行

    为什么 摆脱鼠标系列 - vscode - ctrl+up 光标上移动4行 之前滚动屏幕总是用鼠标,现在改为 ctrl + 上箭头或下箭头 实现起来稍微有些麻烦 实现 需要安装 macros 插件 这 ...

  7. python的替换函数strip(),replace()和re.sub()实例分析

    前记: python是一个非常好用的语言,能够帮忙处理很多日常的耗费体力的事情.今天做一个脚本的时候,遇到了python替换的问题,这里就梳理一下知识点吧. 概念: 1.replace() 基本用法: ...

  8. FFmpeg命令行之ffprobe

    一.简述 ffprobe是ffmpeg命令行工具中相对简单的,此命令是用来查看媒体文件格式的工具. 二.命令格式 在命令行中输入如下格式的命令: ffprobe [文件名] 三.使用ffprobe查看 ...

  9. 直播预约 | 邀您共同探讨“云XR技术如何改变元宇宙的虚拟体验”

    随着数字化时代的到来,元宇宙成为了人们关注的焦点.它是一个虚拟的世界,融合了现实与虚拟的元素,让人们可以以全新的方式进行交互.创作和体验. 云XR技术是元宇宙建设的重要支撑技术之一,元宇宙需要具备高度 ...

  10. vue开发小技巧

    这里分享几个我使用到的vue开发小技巧 一.状态共享 使用Vue进行开发时,随着项目的复杂化,组件个数也逐渐增加,此时我们就面临着一个问题--多组件状态共享.当然有人会说使用Vuex来解决啊,但是如果 ...