juc源码解读七(常用队列)

操作队列的方法

add与offer:插入元素到队尾,队列满了,add抛异常;offer返回false,插入成功都返回true
remove与poll:获取并移除队首元素,队列为空,remove抛异常;poll返回null
element与peek:获取但不移除队首元素,队列为空,element抛异常;peek返回null
put与take:队列满了,空了;分别阻塞
带超时时间的offer与poll,队列满了,超时后,offer返回false,并抛弃该元素;队列为空,超时后,poll返回null;

ArrayBlockingQueue

基于数组,必须指定初始化长度,读,写都用了Reentrantlock锁,具体原因见
https://blog.csdn.net/betaberry/article/details/114406587;
不管是offer还是poll都加了Reentrantlock锁,因为都涉及到index自增或自减操作,所以需要加锁,保证原子性;
ArrayBlockingQueue的并发阻塞是通过ReentrantLock和Condition来实现的;

LinkedBlockingQueue

基于单向链表(基于链表的都是自定义了node节点),默认长度int最大值,有一把写锁,和一把读锁;
并发阻塞是通过ReentrantLock和Condition来实现的;
https://xingfeng.blog.csdn.net/article/details/73087366

LinkedBlockingDeque

基于双向链表,只有一把锁;
linkedBlockingDeque和linkedBlockingQueue的相同点在于:
基于链表
容量可选,不设置的话,就是int的最大值
linkedBlockingDeque和linkedBlockingQueue的不同点在于:
双端链表和单链表
不存在哨兵节点
一把锁+两个条件
linkedBlockingDeque和arrayBlockingQueue的相同点在于:使用一把锁+两个条件维持队列的同步。

SynchronousQueue

无论是哪个方法put,take,offer,poll,最终都是调用transfer方法
有参构造方法传入false,默认采用非公平模式;
公平模式是基于链表,非公平模式是基于栈Stack,其实也是链表。匹配原则都是与队列头或栈顶节点比较,判断是否是同一种模式数据,即根据入参是否有数据分为两种模式,有数据认为是插入操作,没有数据认为是读取操作;同一种模式的节点插入后会先自旋,当达到阈值后,会被挂起,当其他不同模式的节点进来后,当前栈顶元素会与之进行匹配,填充栈顶元素的march字段,唤醒挂起的栈顶元素所在线程,最后再成对出栈,返回数据至调用者。

公平模式

公平模式与非公平模式逻辑几乎一样,
其相同点:
1)请求类型都只有两种data和request,但是非公平模式在“栈”中多了一种FULFILLING类型,表示正在匹配;
2)大体逻辑都是若插入类型相同则先入“栈”或队列,自旋期间若一直没发现新来的可匹配节点,则自旋达到阈值后再挂起当前线程;若插入类型不同则唤醒挂起的线程,出“栈”或列,完成匹配;
不同点:

1)匹配的逻辑不同

公平模式下挂起的队尾节点发现了匹配节点,则执行cas操作,修改挂起的队首节点的item数据域为请求节点的e,接着让挂起的队首节点出列,再唤醒它,此时在awaitFulFill方法中被唤醒的线程判定是item!=e,从而跳出自旋,e表示插入的数据(为null表示请求类型是request);
非公平模式下当挂起的节点发现了匹配节点,则执行tryMatch方法,修改match字段指向匹配节点,唤醒挂起线程,此时在awaitFulFill方法中被唤醒的线程判定match!=null,从而跳出自旋,最后双双出栈;

2)匹配方式不同

公平模式是若请求节点的类型与队列尾节点的类型不同,则让请求节点与队列头节点进行匹配(保证先进先出);
非公平模式是若请求节点模式与栈顶节点的类型不同,则让请求节点与栈顶节点进行匹配;

公平队列的入队列有两种情况:

1)空队列,或队尾节点与当前请求类型是一致的情况,则入队;接着调用awaitFulfill方法,进入自旋,结束自旋的条件是插入的数据所在节点的数据域发生了变化,如
a.当前请求为DATA模式时:e 是请求带来的数据。
刚开始将插入数据e封装为node节点后,item != null 且 item != this 表示请求要传递的数据 put(E e);随后item == this 代表当前Node对应的线程为取消状态;而item == null 表示已经有匹配节点了,并且匹配节点拿走了item数据。
b.当前请求为REQUEST模式时:e == null
刚开始将插入数据e封装为node节点后,item == null 时,正常状态,当前请求仍然未匹配到对应的DATA请求;item == this 表示当前Node对应的线程为取消状态;而item != null 且 item != this 表示当前REQUEST类型的Node已经匹配到一个DATA类型的Node了。
所以若数据域item和e不相等,即可认定已匹配或已取消,返回数据域,结束自旋;否则挂起当前线程。

2)队尾节点与当前请求节点 互补
h.next节点 其实是真正的队头,请求节点与队尾模式不同,需要与队头 发生匹配,这样才能体现先到先得的公平性,具体匹配逻辑是
a.假设当前请求为REQUEST类型e == null,队头 是 DATA类型了,相当于将匹配的DATA Node的数据域清空了,相当于REQUEST 拿走了 它的数据。
b.假设当前请求为DATA类型 e != null, 队头 是 REQUEST类型了,相当于将匹配的REQUEST Node的数据域 填充了,填充了当前DATA 的 数据。相当于传递给REQUEST请求数据了。

非公平模式

1)将请求封装为a2节点,若当前栈为空或者存在栈顶节点a1且与a2的模式一致,则cas修改head为a2,成功则表示入栈成功,否则自旋再次判断,入栈成功后调用核心方法awaitFulfill,这是个自旋方法,自旋超过阈值后,会挂起当前线程,挂起的线程有三种醒来方式;
1.1)被中断唤醒;
1.2)超时唤醒;
1.3)新来了一个节点b1,模式与a2节点相反,匹配上了;
情况1.1与1.2中栈顶节点a2被中断和超时唤醒后,a2的match字段指向自己,a2的状态会变成取消状态,此时会执行clean方法将a2节点清理出栈;情况1.3中被新来节点b1匹配唤醒后,栈顶节点a2的match字段指向新来节点b1,接着更新head指向a2.next,最后将a2与b1再成对出栈;

2)将请求封装为b1节点,其模式与栈顶节点a1模式相反,且a1也不是FULFILLING模式(正在匹配),则会执行以下几步;
2.1)当前栈顶节点若为取消状态,则协助其出栈;
2.2)升级b1的状态为FULFILLING,cas修改head为b1,调用a1的tryMatch方法,令a1.match=b1,唤醒a1.thread,b1与a1结对出栈,head指向a1.next;

3)将请求封装为b1节点,但栈顶节点a1的模式为 FULFILLING模式,表示节点a1和a1.next的正在发生匹配,则b1暂时不入栈,再次调用a1.next.tryMatch方法,协助a1和a1.next出栈,最后b1再次自旋,执行1),2),3)的判断;

PriorityBlockingQueue

底层基于堆

以数组的方式顺序存储。父节点索引在(n-1)/2,这个n代表索引。而最后一个父节点的索引(length-1)/2,length是数组长度,如下图长度为9,所以”最后”一个父节点索引为4,即元素5。添加的新元素时,从元素5开始比较。这里的“最后”并不是指物理位置上的最后一个父节点,而是指接下来的元素继续往哪个父节点放,因为二叉堆的结构是完全二叉树或者近似完全二叉树,所以新插入元素要连续的放,不能跳跃,否则离完全二叉树就会越来越远,如下边的树,当新插入元素11时,此时找到元素5,将11放入其左子节点,而不能放到10下边,尽管10是物理位置上的最后一个节点;

put时若队列满了,则不会阻塞,而是扩容

由于扩容的存在,所以该队列被认为是无界队列。
但是take方法依旧会阻塞。当执行完添加元素后,会调用notEmpty.signal方法唤醒阻塞的线程。

排序的策略由另外两个接口决定

添加的元素要么实现Comparable接口,要么调用PriorityBlockingQueue的两参数构造方法,第二个参数传入Comparator的实现类。

DelayQueue

底层基于堆

基于PriorityQueue,存入元素必须实现Delayed接口,进而重写getDelay方法和compareTo方法。

用于优化阻塞通知的线程对象leader

以take方法为例,这是个自旋方法,通过peek方法检查PriorityQueue队列队首元素是否为null,若为null,则使当前线程等待;若队首元素不为null,则判断队首元素的过期时间是否为已到,若到了则直接poll出,并返回,否则则判断是否有线程正在为了拿到队首元素而等待,即leader是否为null,不为null,则直接调用await方法挂起当前线程,否则,将当前线程赋值给leader,调用超时等待方法awaitNanos(xxx),超时时间更新为队首元素的剩余时间。时间到后,自动醒来的线程会置leader为null,再次自旋,只有超时时间到了,才能取出数据。否则会再次等待。
再看下put方法,里边调用的是offer方法,先执行入列,若队列满了返回false;接着判断插入元素是否插到了队首,若在队首,则执行一次signal方法,唤醒可能在等待的线程,让其再次自旋一次,更新等待队列第一个线程的等待时间;等待的线程被唤醒了,会再次自旋,只有超时时间到了,才能取出数据。否则会再次等待。

排序对象与排序策略

以getDelay方法中的计算得到的过期时间为排序对象,经过compareTo方法排序,一般过期越快的,越先出列。

LinkedTransferQueue

是CocurrentLinkedQueue,SynchronousQueue(公平模式),LinkedBlockingQueue三者的超集,具有这三个的特性。说白点就是当插入类型相同时,该队列可以不阻塞线程,直接返回;也有带超时的;得益于该队列继承了TransferQueue接口,该接口扩展了BlockingQueue接口,增加了tryTransfer方法,具备不阻塞,或带超时特点;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

orcharddd_real

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值