007-阻塞队列

队列

基本模型

表示一组数据的集合,按照规则排列和访问
queue是先进先出FIFO

基本的数据模型包含

  • 一个存放数组的列表
  • 头尾指针
    在这里插入图片描述
    入队:存入tail指向索引位置,tail指针后移一位
    出队:取出head指向索引位置,设置为null,head指针后移一位

Java 接口-队列

java提供顶层接口Queue
在这里插入图片描述

方法返回描述
addboolean有空位插入返回true,否则异常
offerboolean有空位插入返回true,否则返回false
removeE弹出头部节点内容,如果为空则异常
pollE弹出头部节点内容,如果为空则返回null
elementE返回头部节点内容,不弹出,如果为空则异常
peekE返回头部节点内容,不弹出,如果为空则返回null

Java 接口-阻塞队列

调用出队,入队会发生阻塞的队列接口
线程安全
在这里插入图片描述
看一下非继承的方法:

方法返回描述
putvoid插入,并在需要时等待
offer(E, timeOut,timeUtil)boolean插入,并在需要时等待指定的时间,如果超时还没有可用则返回false
takeE弹出头部节点内容,如果有需要则等待
pollE弹出头部节点内容,如果有需要则进行有超时时间的等待,如果超时还没有可用则返回null
remainingCapacityint返回当前剩余容量
drainToint把剩余可用元素转移到另外一个集合,返回转移元素数
drainTo(Collection,int)int把指定个数元素转移到另外一个集合,返回转移元素数

实现 ArrayBlockingQueue

基于数据实现的阻塞队列
在这里插入图片描述

成员变量

在这里插入图片描述
头尾指针只维护队列中的索引
通过ReentrantLock和2个条件来保证并发安全
因为阻塞队列的元素个数是固定的
所以
在队列容量满了的时候入队操作就会等待
在队列容量空了的时候出队操作就会等待
生产、消费者模型

入队 put

在这里插入图片描述
获取锁
如果队列满了就阻塞在notFull条件上(注意这里使用的是while,避免虽然唤醒了,但是其实没有位置入队)
没满就入队
解锁
在这里插入图片描述
x存入tail索引中
tail索引+1,如果==容量则tail索引置为0
:这里不会有问题,因为在上边已经判断如果容量满了就阻塞了
有效数量+1
唤醒notEmpty条件一个线程

出队 take

在这里插入图片描述
获取锁
如果可用量为0
则阻塞在notEmpty条件
否则出队
在这里插入图片描述
取出head索引元素给x
设置head索引元素为null
head索引+1,如果到达容量数则设置head索引为0
有效数量-1
唤醒阻塞在notFull条件上的一个线程

总结

其实就是一个回环链表
在head索引处插入,如果满了就阻塞
弹出tail索引处元素,如果空了就阻塞
插入了就唤醒弹出
弹出了就唤醒插入

缺点

出入对线程使用一把锁,等于只能串行操作

实现 LinkedBlockingQueue

TODO:分析实现
是由Node组成的单向队列
有头节点指针和尾节点指针
有出队锁和条件notEmpty
有入队锁和条件notFull
还有一个当前有效节点数count

初始化

在这里插入图片描述

入队 put

在这里插入图片描述
获取入队锁
循环判断:有效数量==容量就阻塞到notFull条件上
否则enqueue
count = count+1
判断:count + 1 < 容量则唤醒notFull条件上
解锁

如果count==0
则唤醒notEmpty条件
这里是给消费者一个唤醒机会,这里会获取出队锁

在这里插入图片描述
这里第一步把原last的next指向入队的node
然后把last指向入队的node

在这里插入图片描述

出队 take

在这里插入图片描述
获取出队锁
循环判断:有效Node数==0则阻塞在notEmpty条件
否则出队
count = count-1
如果count > 1则唤醒notEmpty条件
解锁

如果count==容量
则唤醒notFull条件
这里是给生产者一个唤醒机会,这里会获取入队锁
在这里插入图片描述
原head的next指向自己
然后返回head的next的item
head指向原head的next

在这里插入图片描述

分析

入队出队锁拆分
有效数量采用原子操作
提高了性能

DelayQueue

在工作中往往会遇到这样的情况,就是一个任务的执行时间是计算后的一个时间,各个任务计算后的时间并不一样,有的早执行,有的晚执行,此时就可以用延时队列

现在来看下使用方法:
在这里插入图片描述
在这里插入图片描述

知道怎么使用后来看下具体的实现

有一个PriorityQueue作为容器
在这里插入图片描述
初始容量为11
可以设置自定义比较器

入队 offer

在这里插入图片描述
获取锁
插入PriorityQueue中
如果PriorityQueue优先级最高的元素就是当前元素
则设置leader为null
唤醒阻塞在available条件上的线程
返回为true
解锁

PriorityQueue入队
在这里插入图片描述
改动次数(modCount)+1
如果有效数量>=队列容量则扩容
如果有效数=0则插入队首
否则计算后插入
有效数+1
返回true
在这里插入图片描述
使用默认比较器的入队
循环比较:
获取父类的索引,(index-1)/2
如果比父节点大或相等,则跳出
如果比父节点小,则把父节点塞入当前位置,index位设置为父索引,进入下一次循环
跳出后:将元素设置到index索引位置

总结:不保证队列的顺序性,但是保证第一个接口一定是最小的
并且没有挨个比较提升了性能
并且在移位的时候使用的是无符号右移,不会出现小数

那就有个问题既然不保证顺序性那下一次取数据的时候岂不是乱了?
其实是在每次出队的时候会重新再计算下最小值具体在 poll代码里。

出队take

在这里插入图片描述
获取锁
循环:
取出第一个
如果为null,则等待在available条件上
否则获取其延时剩余时间
如果小于0则出队返回

判断leader不为null,则等待在available条件上
否则设置leader为当前线程并await延时时间
延时结束后判断当前线程如果是leader的话设置leader为null

结束后判断如果leader==null并且head不为null,则唤醒available条件上的队列
解锁

poll()

在这里插入图片描述
在返回前进行了切换, 从0开始重新检查最小值

总结

从代码可以看出,如果线程一旦获取到锁就会设置leader
如果取出来的第一个ok则出队
如果不ok则await,重新进入循环,下次循环取一下head重新去判断
注意这里其他线程获取的任务如果已经ok 则不会进入leader机制
否则不是leader就直接进入等待,避免了线程安全问题

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值