博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

阻塞的队列

Posted on 2023-07-26 12:19  小飞龙(Jack)  阅读(1)  评论(0编辑  收藏  举报

BLockingQueue是一个阻塞的队列,最典型的应用场景就是生产者和消费者模式。

生产者和消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此并不直接通信,而是通过阻塞队列进行通信,所以生产者生产完数据后不用等待消费者进行处理,而是直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列中获取数据,阻塞队列就相当于一个缓冲区,平衡生产者和消费者的处理能力。

在Java中她只是一个接口,它的实现类有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。


注意事项:
BlockingQueue不接受null元素。 实现在尝试add 、 put或offer null抛出NullPointerException 。 null值用作标记值以指示poll操作失败。
BlockingQueue可能有容量限制。 在任何给定的时间,它可能有一个remainingCapacity容量,超过该容量就不能put不阻塞的情况下put其他元素。 没有任何内在容量限制的BlockingQueue总是报告Integer.MAX_VALUE的剩余容量。
BlockingQueue实现主要用于生产者-消费者队列,但另外还支持Collection接口。 因此,例如,可以使用remove(x)从队列中remove(x)任意元素。 然而,这样的操作通常执行得不是很有效,并且仅用于偶尔使用,例如当排队的消息被取消时。
BlockingQueue实现是线程安全的。 所有排队方法都使用内部锁或其他形式的并发控制以原子方式实现其效果。 然而,大量的Collection操作addAll , containsAll , retainAll和removeAll不一定原子除非在实现中另有规定执行。 因此,例如,在仅添加c某些元素后, addAll©可能会失败(抛出异常)。
使用示例,基于典型的生产者-消费者场景。 请注意,一个BlockingQueue可以安全地与多个生产者和多个消费者一起使用。
1.ArrayBlockingQueue:
由数组支持的有界阻塞队列。 该队列对元素 FIFO(先进先出)进行排序。 队列的头部是在队列中停留时间最长的那个元素。 队列的尾部是在队列中停留时间最短的那个元素。 新元素插入队列尾部,队列检索操作获取队列头部元素。
这是一个经典的“有界缓冲区”,其中一个固定大小的数组保存由生产者插入并由消费者提取的元素。 容量一旦创建,就无法更改。 尝试put元素放入已满队列将导致操作阻塞; 尝试从空队列中take元素也会类似地阻塞。
此类支持用于排序等待生产者和消费者线程的可选公平策略。 默认情况下,不保证此顺序。 但是,在公平性设置为true构造的队列以 FIFO 顺序授予线程访问权限。 公平通常会降低吞吐量,但会减少可变性并避免饥饿。

2.LinkedBlockingQueue:
基于链表实现的有界阻塞队列。 该队列对元素 FIFO(先进先出)进行排序。 队列的头部是在队列中停留时间最长的那个元素。 队列的尾部是在队列中停留时间最短的那个元素。 新元素插入队列尾部,队列检索操作获取队列头部元素。 链接队列通常比基于数组的队列具有更高的吞吐量,但在大多数并发应用程序中的可预测性能较差。

3. DelayQueue
DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
使用场景:
  DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列

4.PriorityBlockingQueue
一个无界阻塞队列,它使用与类PriorityQueue相同的排序规则并提供阻塞检索操作。 虽然这个队列在逻辑上是无界的,但由于资源耗尽(导致OutOfMemoryError ),尝试添加可能会失败。 此类不允许null元素。 依赖于自然排序的优先级队列也不允许插入不可比较的对象(这样做会导致ClassCastException )。
此类及其迭代器实现了Collection和Iterator接口的所有可选方法。 方法iterator()提供的 Iterator 和方法spliterator()中提供的spliterator()不能保证以任何特定顺序遍历 PriorityBlockingQueue 的元素。 如果您需要有序遍历,请考虑使用Arrays.sort(pq.toArray()) 。 此外, drainTo方法可用于按优先级顺序删除部分或所有元素,并将它们放置在另一个集合中。
对此类的操作不保证具有相同优先级的元素的顺序。 如果您需要强制排序,您可以定义自定义类或比较器,这些类或比较器使用辅助键来打破主要优先级值之间的联系。 例如,这是一个将先进先出打破平局应用于可比元素的类。 要使用它,您需要插入一个new FIFOEntry(anEntry)而不是一个普通的条目对象。

5.SynchronousQueue
一个阻塞队列,其中每个插入操作都必须等待另一个线程执行相应的移除操作,反之亦然。 同步队列没有任何内部容量,甚至没有容量。 您无法peek同步队列,因为元素仅在您尝试删除它时才存在; 你不能插入一个元素(使用任何方法),除非另一个线程试图删除它; 你不能迭代,因为没有什么可以迭代的。 队列的头部是第一个排队的插入线程试图添加到队列中的元素; 如果没有这样的排队线程,则没有元素可用于删除, poll()将返回null 。 对于其他Collection方法(例如contains ), SynchronousQueue充当空集合。 此队列不允许null元素。
同步队列类似于 CSP 和 Ada 中使用的集合通道。 它们非常适合切换设计,在这种设计中,在一个线程中运行的对象必须与在另一个线程中运行的对象同步,以便将某些信息、事件或任务交给它。
此类支持用于排序等待生产者和消费者线程的可选公平策略。 默认情况下,不保证此顺序。 但是,在公平性设置为true构造的队列以 FIFO 顺序授予线程访问权限