队列的使用注意点

队列通常使用链表或数组作为元素的基础存储。

  1. 队列的大小需要约束。

如果允许内存中的队列不受限制,那么对于许多类别的问题,它可以不受限制地增长,直到它达到灾难性失败的地步,因为它耗尽了内存。这发生在生产者超过消费者的时候。无界队列在系统中可能很有用,因为生产者保证不会超过消费者,而且内存是一种宝贵的资源,但是如果这个假设不成立,队列无限制地增长,那么总是存在风险。为了避免这种灾难性的结果,队列的大小通常受到限制(有界)。保持队列的约束要求它要么是数组支持的,要么是主动跟踪其大小。

  1. 队列的常态是接近满或者接近空

队列的实现往往在头部、尾部和大小变量上有写竞争。在使用时,由于消费者和生产者之间的速度差异,队列通常总是接近于满或接近于空。它们很少在生产和消费的速度均衡的中间地带运行。这种总是满载或空载的倾向导致了高水平的争夺和昂贵的缓存一致性。问题是,即使头部和尾部机制使用不同的并发对象(如锁或CAS变量)进行分离,它们通常也会占用同一个缓存行。

  1. 队列里面的竞争很多

除了在队列上使用单一的大颗粒锁之外,还需要管理生产者对队列头部的要求,消费者对队列尾部的要求,以及中间节点的存储,这些问题使得并发实现的设计非常复杂。在整个队列上的大颗粒锁的投放和获取操作很容易实现,但对吞吐量来说是一个很大的瓶颈。如果在队列的语义中把并发关注点分开,那么除了单一生产者-单一消费者的实现之外,其他的实现都会变得非常复杂。

  1. java语境中队列是垃圾的重要来源

在Java中,使用队列还有一个问题,因为它们是垃圾的重要来源。首先,对象必须被分配并放在队列中。其次,如果是以链接列表为基础,必须分配代表列表节点的对象。当不再被引用时,所有这些被分配来支持队列实现的对象都需要被重新回收。

Queues typically use either linked-lists or arrays for the underlying storage of elements. If an in-memory queue is allowed to be unbounded then for many classes of problem it can grow unchecked until it reaches the point of catastrophic failure by exhausting memory. This happens when producers outpace the consumers. Unbounded queues can be useful in systems where the producers are guaranteed not to outpace the consumers and memory is a precious resource, but there is always a risk if this assumption doesn’t hold and queue grows without limit. To avoid this catastrophic outcome, queues are commonly constrained in size (bounded). Keeping a queue bounded requires that it is either array-backed or that the size is actively tracked.

Queue implementations tend to have write contention on the head, tail, and size variables. When in use, queues are typically always close to full or close to empty due to the differences in pace between consumers and producers. They very rarely operate in a balanced middle ground where the rate of production and consumption is evenly matched. This propensity to be always full or always empty results in high levels of contention and/or expensive cache coherence. The problem is that even when the head and tail mechanisms are separated using different concurrent objects such as locks or CAS variables, they generally occupy the same cache-line.

The concerns of managing producers claiming the head of a queue, consumers claiming the tail, and the storage of nodes in between make the designs of concurrent implementations very complex to manage beyond using a single large-grain lock on the queue. Large grain locks on the whole queue for put and take operations are simple to implement but represent a significant bottleneck to throughput. If the concurrent concerns are teased apart within the semantics of a queue then the implementations become very complex for anything other than a single producer – single consumer implementation.

In Java there is a further problem with the use of queues, as they are significant sources of garbage. Firstly, objects have to be allocated and placed in the queue. Secondly, if linked-list backed, objects have to be allocated representing the nodes of the list. When no longer referenced, all these objects allocated to support the queue implementation need to be re-claimed.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值