在阿里面试官面前现场手撕DelayQueue源码!(下)

简介: 在阿里面试官面前现场手撕DelayQueue源码!(下)

4 新增数据

先看看继承自 BlockingQueue 的方法

put

  • 将指定的元素插入此延迟队列。 由于队列无界,因此此方法将永远不会阻塞.
  • image.png
  • 可以看到 put 调用的是 offer

DelayQueue#offer

  • 将指定的元素插入此延迟队列
  • image.png

执行流程

1.加锁

2.元素添加到优先级队列中

3.检验元素是否为队首,是则设置 leader 为null, 并唤醒一个消费线程

4.解锁

其内部调用的是 PriorityQueue 的 offer 方法

PriorityQueue#offer

将指定的元素插入此优先级队列.

public boolean offer(E e) {
    // 若元素为 null,抛NPE
    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;
}

执行流程

  1. 元素判空
  2. 队列扩容判断
  3. 根据元素的 compareTo 方法进行排序,希望最终排序的结果是从小到大的,因为想让队首的都是过期的数据,需要在 compareTo 方法实现.

5 取数据

take

检索并删除此队列的头,如有必要,请等待直到延迟过期的元素在此队列上可用

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        // 获取可中断锁
        lock.lockInterruptibly();
        try {
            for (;;) {
                // 从优先级队列中获取队首
                E first = q.peek();
                if (first == null)
                    // 队首为 null,说明无元素,当前线程加入等待队列,并阻塞
                    available.await();
                else {
                    // 获取延迟时间
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        // 已到期,获取并删除头部元素
                        return q.poll();
                    first = null; // 在等待时不要保留引用
                    if (leader != null)
                        available.await();
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            // 线程节点进入等待队列
                            available.awaitNanos(delay);
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            // 若leader == null且还存在元素,则唤醒一个消费线程
            if (leader == null && q.peek() != null)
                available.signal();
            // 解锁
            lock.unlock();
        }
    }

执行流程

  1. 加锁
  2. 取出优先级队列的队首
  3. 若队列为空,阻塞
  4. 若队首非空,获得这个元素的delay时间值,如果first的延迟delay时间值为0的话,说明该元素已经到了可以使用的时间,调用poll方法弹出该元素,跳出方法
  5. 若first的延迟delay时间值非0,释放元素first的引用,避免内存泄露
  6. 循环以上操作,直至return

take 方法是会无限阻塞,直到队头的过期时间到了才会返回.

如果不想无限阻塞,可以尝试 poll 方法,设置超时时间,在超时时间内,队头元素还没有过期的> 话,就会返回 null.

6 解密 leader 元素

leader 是一个Thread元素,表示当前获取到锁的消费者线程.

  • 以take代码段为例
  • image.png
  • 若 leader 非 null,说明已有消费者线程获取锁,直接阻塞当前线程.


若 leader 为 null,把当前线程赋给 leader,并等待剩余的到期时间,最后释放 leader.

这里假设有多个消费者线程执行 take 取数据,若没有leader != null 判断,这些线程都会无限循环,直到返回第一个元素,这显然很浪费系统资源. 所以 leader 在这里相当于一个线程标识,避免消费者线程的无脑竞争.

注意这里因为first是队首的引用,阻塞时会有很多线程同时持有队首引用,可能导致内存溢出,所以需要手动释放.

image.png

7 总结

DelayQueue 使用排序和超时机制即实现了延迟队列.充分利用已有的 PriorityQueue 排序功能,超时阻塞又恰当好处的利用了锁的等待,在已有机制的基础上进行封装.在实际开发中,可以多多实践这一思想,使代码架构具备高复用性.

目录
相关文章
|
10月前
|
存储 关系型数据库 MySQL
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
|
9月前
|
监控 Java 数据安全/隐私保护
阿里面试:SpringBoot启动时, 如何执行扩展代码?你们项目 SpringBoot 进行过 哪些 扩展?
阿里面试:SpringBoot启动时, 如何执行扩展代码?你们项目 SpringBoot 进行过 哪些 扩展?
|
8月前
|
负载均衡 架构师 Cloud Native
阿里面试:服务与发现 ,该选 CP 还是 AP?为什么?
阿里面试:服务与发现 ,该选 CP 还是 AP?为什么?
阿里面试:服务与发现 ,该选  CP 还是 AP?为什么?
|
9月前
|
SQL Java 数据库连接
阿里腾讯互联网公司校招 Java 面试题总结及答案解析
本文总结了阿里巴巴和腾讯等互联网大厂的Java校招面试题及答案,涵盖Java基础、多线程、集合框架、数据库、Spring与MyBatis框架等内容。从数据类型、面向对象特性到异常处理,从线程安全到SQL优化,再到IOC原理与MyBatis结果封装,全面梳理常见考点。通过详细解析,帮助求职者系统掌握Java核心知识,为校招做好充分准备。资源链接:[点击下载](https://pan.quark.cn/s/14fcf913bae6)。
354 2
|
9月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
434 3
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
11月前
|
存储 NoSQL Redis
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 + 无锁架构 + EDA架构 + 异步日志 + 集群架构
阿里面试:Redis 为啥那么快?怎么实现的100W并发?说出了6大架构,面试官跪地: 纯内存 + 尖端结构 +  无锁架构 +  EDA架构  + 异步日志 + 集群架构
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
例如,在一个有 10 个节点的系统中,增加一个新节点,只会影响到该新节点在哈希环上相邻的部分数据,其他大部分数据仍然可以保持在原节点,大大减少了数据迁移的工作量和对系统的影响。狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由”。在 3 - 5 年的中期阶段,随着业务的稳定发展和市场份额的进一步扩大,订单数据的增长速度可能会有所放缓,但仍然会保持在每年 20% - 30% 的水平。
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer

热门文章

最新文章