java延时队列

news2024/11/18 17:35:53

二、延时队列使用场景

那么什么时候需要用延时队列呢?常见的延时任务场景 举栗子:

  1. 订单在30分钟之内未支付则自动取消。
  2. 重试机制实现,把调用失败的接口放入一个固定延时的队列,到期后再重试。
  3. 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
  4. 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
  5. 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议。
  6. 关闭空闲连接,服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。
  7. 清理过期数据业务。比如缓存中的对象,超过了空闲时间,需要从缓存中移出。
  8. 多考生考试,到期全部考生必须交卷,要求时间非常准确的场景。

三、解决办法

  1. 定期轮询(数据库等)
  2. JDK DelayQueue
  3. JDK Timer
  4. ScheduledExecutorService 周期性线程池
  5. 时间轮(kafka)
  6. 时间轮(Netty的HashedWheelTimer)
  7. Redis有序集合(zset)
  8. zookeeper之curator
  9. RabbitMQ
  10. Quartz,xxljob等定时任务框架
  11. Koala(考拉)
  12. JCronTab(仿crontab的java调度器)
  13. SchedulerX(阿里)
  14. 有赞延迟队列
  • 那我们第一篇先来实战JDK的DelayQueue,万祖归宗,万法同源,学会了最基础的Queue,就不愁其他的了
  • 后续再写几篇使用Redis,Zk,MQ的一些机制,实战分布式情况下的使用

四、先认亲

延时队列,首先,它是一种队列,队列意味着内部的元素是有序的,元素出队入队是有方向性的,元素从一端进入,从另一端取出。

其次,延时队列,最重要的特性就体现在它的延时属性上,跟普通的队列不一样的是,普通队列中的元素总是等着希望被早点取出处理,而延时队列中的元素则是希望被在指定时间得到取出和处理,所以延时队列中的元素是都是带时间属性的,通常来说是需要被处理的消息或者任务。

一言以蔽之曰 : 延时队列就是用来存放需要在指定时间被处理的元素的队列。

1) DelayQueue 是谁,上族谱

 

看的出来到DelayQueue这一代已经第五代传人了,

 

要知道 DelayQueue自幼生在八戒家,长大就往外面拉,熊熊烈火它不怕,水是水来渣是渣。

不过它真的是文韬武略,有一把ReentrantLock就是它的九齿钉耙,抗的死死の捍卫着自己的PriorityQueue.

有典故曰:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
           implements BlockingQueue<E> {
// 用于控制并发的 可重入 全局 锁
private final transient ReentrantLock lock = new ReentrantLock();
// 根据Delay时间排序的 无界的 优先级队列
private final PriorityQueue<E> q = new PriorityQueue<E>();
// 用于优化阻塞通知的线程元素leader,标记当前是否有线程在排队(仅用于取元素时)
private Thread leader = null;
// 条件,用于阻塞和通知的Condition对象,表示现在是否有可取的元素
private final Condition available = lock.newCondition();
 
       /**
        * 省洛方法代码.....  你们懂我的省洛吗?
        */
复制代码
  • 注释的已经很清楚他们的意思了,也具备了并发编程之艺术的 锁,队列,状态(条件)
  • 他的几个方法也是通过 锁-->维护队列-->出队,入队-->根据Condition进行条件的判断-->进行线程之间的通信和唤起
  • 以支持优先级无界队列的PriorityQueue作为一个容器,容器里面的元素都应该实现Delayed接口,在每次往优先级队列中添加元素时以元素的过期时间作为排序条件,最先过期的元素放在优先级最高。
  • DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

2) 优先级队列 PriorityQueue

因为我们的DelayQueue里面维护了一个优先级的队列PriorityQueue 简单的看下:

    //默认容量11
     private static final int DEFAULT_INITIAL_CAPACITY = 11;
    //存储元素的地方 数组
    transient Object[] queue; // non-private to simplify nested class access
    //元素个数
    private int size = 0;
    //比较器
    private final Comparator<? super E> comparator;
复制代码
  1. 默认容量是11;
  2. queue,元素存储在数组中,这跟我们之前说的堆一般使用数组来存储是一致的;
  3. comparator,比较器,在优先级队列中,也有两种方式比较元素,一种是元素的自然顺序,一种是通过比较器来比较;
  4. modCount,修改次数,有这个属性表示PriorityQueue也是fast-fail的;
  5. PriorityQueue不是有序的,只有堆顶存储着最小的元素;
  6. PriorityQueue 是非线程安全的;

3) DelayQueue的方法简介

  • 入队方法 : 若添加的元素是队首(堆顶)元素,就把leader置为空,并唤醒等待在条件available上的线程;
public boolean add(E e) {    return offer(e);}
public void put(E e) {    offer(e);}
public boolean offer(E e, long timeout, TimeUnit unit) {    return offer(e);}
public boolean offer(E e) {    
    final ReentrantLock lock = this.lock;    
    lock.lock();   //加锁 因为优先队列线程不安全
    try {
        q.offer(e);  //判断优先级 进行入队      
    if (q.peek() == e) {    //-----[1]
        //leader记录了被阻塞在等待队列头生效的线程 新增一个元素到队列头,
        //表示等待原来队列头生效的阻塞的线程已经失去了阻塞的意义
        //,此时需要获取新的队列头进行返回了
        leader = null;      
        //获取队列头的线程被唤起,主要有两种场景:
        //1. 之前队列为空,导致被阻塞的线程
        //2. 之前队列非空,但是队列头没有生效(到期)导致被阻塞的线程
        available.signal();     
    }        
        return true; //因为是无界队列 所以添加元素肯定成功  直到OOM
    } finally {    
        lock.unlock();   //释放锁
    }
}
复制代码

offer()方法,首先获取独占锁,然后添加元素到优先级队列,由于q是优先级队列,所以添加元素后,peek并不一定是当前添加的元素,如果[1]为true,说明当前元素e的优先级最小也就即将过期的,这时候激活avaliable变量条件队列里面的线程,通知他们队列里面有元素了。

  • 出队方法 take()

请看我详细的注释,绝不是蜻蜓点水

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock; //获取锁 
    lock.lockInterruptibly();   //可中断锁 可以自行了解一下 嘻嘻嘻嘻...
    try {
        for (;;) {//会写死循环的都是高手
            E first = q.peek();//get队头元素
            if (first == null)
                // 队列头为空,则阻塞,直到新增一个入队为止(1)
                available.await();
            else {
                long delay = first.getDelay(NANOSECONDS);//获取剩余时间
                if (delay <= 0)
                    // 若队列头元素已生效,则直接返回(2)
                    return q.poll();
                first = null; // don't retain ref while waiting 等待的时候不能引用,表示释放当前引用的(3)
                if (leader != null)
                    // leader 非空时,表示有其他的一个线程在出队阻塞中 (4.1)
                    // 此时挂住当前线程,等待另一个线程出队完成
                    available.await();
                else {
                    //标识当前线程处于等待队列头生效的阻塞中 (4.2.1)
                    Thread thisThread = Thread.currentThread(); 
                    leader = thisThread;
                    try {
                        // 等待队列头元素生效(4.2.2)
                        available.awaitNanos(delay);
                    } finally {
                        //最终释放当前的线程 设置leader为null (4.2.3)
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }     //(5)
    } finally {
        if (leader == null && q.peek() != null)
            // 当前线程出队完成,通知其他出队阻塞的线程继续执行(6)
            available.signal();
            lock.unlock();//解锁结束
    }
}
复制代码

那么,下面的结论肉眼可见:

  1. 如果队列为空,则阻塞,直到有个线程(生产者投递数据)完成入队操作
  2. 获取队列头,若队列头已生效,则直接返回
  3. 未生效则释放当前引用
  4. 当队列头部没有生效时候:
    1. 若有另一个线程已经处于等待队列头生效的阻塞过程中,则阻塞当前线程,直到另一个线程完成出队操作
    2. 若没有其他线程阻塞在出队过程中,即当前线程为第一个获取队列头的线程
      • 标识当前线程处于等待队列头生效的阻塞中(leader = thisThread
      • 阻塞当前线程,等待队列头生效
      • 队列头生效之后,清空标识(leader=null)
  5. 再次进入循环,获取队列头并返回
  6. 最后,当前线程出队完成,通知其他出队阻塞的线程继续执行

4)Delayed

public interface Delayed extends Comparable<Delayed> { 
    long getDelay(TimeUnit unit);
}
复制代码

据情报显示:Delayed是一个继承自Delayed的接口,并且定义了一个Delayed方法,用于表示还有多少时间到期,到期了应返回小于等于0的数值。

很简答就是定义了一个,一个哈,一个表延迟的接口,就是个规范接口,目的就是骗我们去实现它的方法.哼~

五、后总结

  • 这就是单机的不好处,也是一个痛点,所以肯定是不太适合订单量特别大的场景 大家也要酌情考虑和运用
  • 相对于同等量级的数据库轮询操作来说,真是节省了不少数据库的压力和连接,还是值得一用的,我们可以只保存订单的id到延时实例中,这样缩减队列单个实例内存存储
  • 那还有技巧就是更新的时候注意控制好幂等性,控制好幂等性,会让你轻松很多,顺畅很多,但是数据量大了,要蛀牙的哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370366.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XML调用 CAPL Test Function

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

数据结构:完全二叉树开胃菜小练习

目录 一.前言 二.完全二叉树的重要结构特点 三.完全二叉树开胃菜小练习 1.一个重要的数学结论 2.简单的小练习 一.前言 关于树及完全二叉树的基础概念(及树结点编号规则)参见:http://t.csdn.cn/imdrahttp://t.csdn.cn/imdra 完全二叉树是一种非常重要的数据结构: n个结点的…

22-基于分时电价条件下家庭能量管理策略研究MATLAB程序

参考文献&#xff1a;《基于分时电价和蓄电池实时控制策略的家庭能量系统优化》参考部分模型《计及舒适度的家庭能量管理系统优化控制策略》参考部分模型主要内容&#xff1a;主要做的是家庭能量管理模型&#xff0c;首先构建了电动汽车、空调、热水器以及烘干机等若干家庭用户…

【C++入门第二期】引用 和 内联函数 的使用方法及注意事项

前言引用的概念初识引用区分引用和取地址引用与对象的关系引用的特性引用的使用场景传值和引用性能比较引用和指针的区别内联函数内联函数的概念内联函数的特性前言 本文主要学习的是引用 及 内联含函数&#xff0c;其中的引用在实际使用中会异常舒适。 引用的概念 概念&…

基于SpringBoot的企业资产管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

蓝桥2.24训练

1&#xff0c;奇怪的函数 P2759 奇怪的函数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 1这道题有两个点&#xff0c;一个是求数的位数 2&#xff0c;用整数二分求出的位数与n比较 #include <bits/stdc.h> using namespace std; typedef long long ll; ll n; int ma…

【华为OD机试模拟题】用 C++ 实现 - 矩阵最值(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

flutter- JSON解析框架使用方法json_serializable

对于目前来说&#xff0c;大部分的API网络请求的通讯内容数据格式都是JSON。JSON返回的都是字符串&#xff0c;假如要取到data里面的id&#xff0c;去直接字符串截取肯定是不行的&#xff0c;要通过一定的方式把它解析成Map或者解析成对象&#xff0c;再去处理它。像一些简单的…

YOLOv5深度剖析

目录 YOLOv5算法概述 网络结构 backbone Conv模块&#xff1a; C3模块 SPPF neck 图形特征与语义特征 特征金字塔 head YOLOv5算法原理 以感受野理解网格 框回归以及分类 YOLOv5细节 消除网格敏感度 b. 正样本匹配 c. 损失计算 YOLOv5算法概述 YOLOv5与历代YO…

【异常】导出Excel异常This archive contains unclosed entries.

一、异常说明 二、定位问题代码 一看问题, 上下文都是与订单相关的内容。 查询代码的使用地方,发现出现在这个Mybatis的select语句中 查看备注,发现是订单物流,那就没跑了, 肯定是商城的物流模块出了问题 那是什么地方用到了这个物流模块而且涉及到导出功能呢? 经验告诉…

代码随想录算法训练营day42 |动态规划之背包问题 11.分割等和子集 1049. 最后一块石头的重量 II 494. 目标和 474.一和零

day4211.分割等和子集1.dp数组的含义2.确定递推公式3.dp数组初始化4.确定遍历顺序5.打印dp数组1049. 最后一块石头的重量 II1.确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组494. 目标和1.确定dp数组以及下标的含义2.确定递推公式3.…

ChatGPT懂SAP吗?跟ChatGPT聊完后,我暂时不担心会失业

我弄了个ChatGPT账号。随便问了一下他SAP的问题。贴出来跟大家分享一下。 问题1&#xff1a;我是 SAP 开发工程师&#xff0c;你有什么可以帮到我。 作为一个语言模型&#xff0c;我可以为您提供关于SAP开发的一般性建议&#xff0c;以及关于如何使用SAP软件的一些技巧。以下是…

【华为OD机试模拟题】用 C++ 实现 - 合规数组(2023.Q1)

最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…

流感来了,这类人最容易感染!

最近有学校因多名学生发热停课&#xff0c;浙江多地疾控也提醒大家现在是进入了甲流高发期。今天就来讲一讲甲流该如何防护。首先甲流与普通感冒不同&#xff0c;感冒病原体是鼻病毒、冠状病毒、副流感病毒等。流感病毒是正粘病毒科&#xff0c;根据核蛋白和基质蛋白M1抗原性的…

六千字让你明白什么是数字孪生?

文章目录1. 背景2. 数字孪生基础2.1 概念2.2 价值3. 技术生态3.1 技术体系3.2 核心技术3.2.1 多领域、多尺度融合建模3.2.2 数据驱动与物理模型融合的状态评估3.2.3 数据采集和传输3.2.4 全生命周期数据管理3.2.5 虚拟现实呈现3.2.6 高性能计算3.3 建设3.3.1 重点3.3.1.1 数字孪…

3款强大且实用的电脑软件,颠覆你的认知,值得一试

闲话少说&#xff0c;直上狠货。 1、一个木函 一个木函仅一张照片的体积&#xff0c;却提供了与日常、图片、设备、文件、文字处理等等相关的80多种工具&#xff0c;相当实用&#xff0c;更牛的是&#xff0c;完全免费&#xff0c;无任何弹屏广告。一个木函体积小&#xff0c;简…

力扣-游戏玩法分析

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;511. 游戏玩法分析二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结…

【Acwing 周赛复盘】第91场周赛复盘(2023.2.18)

【Acwing 周赛复盘】第91场周赛复盘&#xff08;2023.2.18&#xff09; 周赛复盘 ✍️ 本周个人排名&#xff1a;1286/3115 AC情况&#xff1a;2/3 这是博主参加的第六次周赛&#xff0c;周赛当晚有事&#xff0c;是后来定时自测的 &#x1f602; 在 20 分钟内 AC 了 2 题&…

计算机408考研先导课---C语言难点2

目录 一、字符型数据与字符串型数据的比较 1、字符型数据特点 2、字符串型数据特点 二、字符数组 1、定义 2、输入输出 ①输入 ②输出 3、字符处理函数 ①put函数 ②gets函数 ③strcat函数 ④strcpy函数 ⑤strcmp函数 ⑥strlen函数 ⑦strlwr函数 ⑧strup…

LeetCode练习三:链表

文章目录一、链表基础1.1 无序表&#xff08;UnorderedList&#xff09;1.1.2 双向链表1.1.3 循环链表1.2 链表的基本操作1.2.1 定义链表结构1.2.2 建立线性链表1.2.3 求线性链表的长度1.2.4 查找元素1.2.5 插入元素1.2.6 改变元素1.2.7 删除元素1.3 有序表OrderedList1.4 链表…