fscanf返回值被忽略怎么解决_无头队列怎么实现:DelayQueue用法、实现及Leader-Follower模式...

无头队列怎么实现:DelayQueue用法、实现及Leader-Follower模式

作者:吴潇 职位:Java软件工程师

原创声明

这是本人署名原创文章,未经许可不支持转载且请勿抄袭。本公众号的所有文章均为本人原创。为了容易理解和记忆,文章以图解为主、代码为辅,忽略不重要细节,但保留关键细节。如果您感兴趣,欢迎关注!

如果需要给对象关联一个延迟时间,让这个对象必须在等待一段时间之后才可以被使用(处理),否则这个对象对使用者来说不可见(例如,支付宝的快速到账延迟到账功能),这样的任务怎么实现?最简单的方法是使用JDK并发工具中的Delay接口和DelayQueue队列。

用Delayed接口标记延迟对象

首先,需要把类标记成支持延迟特性的,即实现Delayed接口。Delayed接口要求实现getDelay和compareTo方法。其中,getDelay方法负责控制对象如何延迟,它应该返回一个剩余时间;compareTo负责定义不同延迟对象之间的相对顺序(用于堆排序)。例如:

b83b9cf3b73f7a273806d0febd03e54f.png

getDelay方法的返回值将用于排序,因此,它必须与compareTo保持一致。其实,我们可以在getDelay方法中实现一些特定逻辑,以灵活控制延迟时间,例如延迟一个固定的时间,延迟一个随机的时间,或者根据流量/负载等动态调整延迟时间。实现getDelay的时候要注意根据传入的TimeUnit返回对应不同单位的时间数值。

将Delayed对象放入DelayQueue(延迟队列)

DelayQueue是JDK并发包提供的延迟任务队列,是一个无界阻塞队列,只能存储Delayed对象。当Delayed对象进入DelayQueue之后,只有其延迟时间结束才可以从队列取出,否则对于外部来说不可见。队列头部存放尚未被取出的、最早到达结束时间的Delayed对象,如果队列中的对象全都没到达结束时间(getDelay返回值大于0),则队列头部是空的(poll将返回null)。此时,队列中有对象但是看上去是没有头部的,因而称为无头队列

fe0dce91ab2100b1e375b7ed7aebce7f.png

但是注意,DelayQueue中的对象不是按照getDelay返回值从小到大排序的,它实际上是一个堆(堆顶元素最小)。对这个队列的任何遍历操作都不保证是排好序的。

如何使用DelayQueue

生产者负责创建Delayed对象,并且把对象放入DelayQueue(可以让每个对象的getDelay方法执行不同的代码,也可以让它们都执行相同的代码、只是参数不同)。生产者则从DelayQueue取出对象直接使用,不必判断延迟时间是否结束。假如对象放入DelayQueue之后,对象的延迟时间是动态变化的,则需要把这个对象从队列删除然后再重新放进队列(注意,JDK官方文档没有介绍这个问题)

DelayQueue的实现原理

DelayQueue内部主要依赖PriorityQueue(非线程安全的)实现,即它把Delayed对象放入PriorityQueue,由PriorityQueue保证延迟时间最小的对象总是位于头部。当往DelayQueue中放入对象时,把对象放入内部的PriorityQueue即可;当从DelayQueue取出对象时,先到查看一下内部PriorityQueue的队列头部的对象(通过peek),如果getDelay小于等于0才会返回,否则均返回null。在放入对象(add/offer/put)和取出对象的时候(poll/take),调整线程的等待状态,这是基于Leader-Follower模式实现的,见后文分析。

DelayQueue是一个线程安全的类,所有操作都是通过ReentrantLock保证线程安全性,因而不适合高并发的场景。线程的等待和阻塞是用与ReentrantLock关联的条件队列实现的。

Leader-Follower模式

因为可能有多个线程在DelayQueue上进行等待(从头部获取元素),所以,为了减少每个等待线程多次调用getDelay的计算量,DelayQueue采用了Leader-Follower模式,即只让一个Leader线程进行timed wait(根据getDelay的返回值计算等待时间),而其他Follower线程进行indefinite wait并且需要被Leader线程唤醒(通过signal唤醒)。

f9c54625e31048b29013eb648f29f939.png

Leader-Follower模式的目标

  1. 减少getDelay调用次数,让getDelay只被Leader线程调用一次。
  2. 如果在等待的过程中有新对象进入队列,则重新计算等待时间。Leader-Follower模式保证只需要调整Leader线程的等待时间,不需要调整其他Follower线程。

Leader-Follower模式在DelayQueue中的实现

  1. 当队列为空的时候,所有线程都进行indefinite wait,此时没有Leader。
  2. 当放入一个对象且它位于成为PriorityQueue头部时,重新选择Leader并唤醒。
  3. 当Leader线程成功从队列取走对象之后,如果PriorityQueue不为空,则唤醒一个Follower线程让它成为新Leader。
  4. 当一个线程从DelayQueue取对象时,如果此时有对象则直接取出并且返回;如果没有,则判断是否有Leader,没有Leader则自己成为Leader并进入timed wait,如果已经有Leader,则进入indefinite wait。

通过使用Leader-Follower模式,DelayQueue减少了对象的getDelay方法的调用开销,并且还保证队列中的对象动态发生变化之后,等待线程依然能及时被唤醒并从队列取出对象,实时性得到了保证。

最后再强调一下,如果Delayed接口的getDelay方法的返回值是动态变化的,那么一定要在getDelay发生变化之后,把这个对象从DelayQueue删除,然后再重新放进去。DelayQueue不能自动处理getDelay会发生变化的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值