关于spring:LinkedBlockingQueue-和-ConcurrentLinkedQueue的区别

4次阅读

共计 2971 个字符,预计需要花费 8 分钟才能阅读完成。

1. 简略的开篇

LinkedBlockingQueueConcurrentLinkedQueue 是 Java 高并发场景中最常应用的队列。只管这两个队列常常被用作并发场景的数据结构,但它们之间仍有轻微的特色和行为差别。
在这篇文章中,我将和大家一起探讨这两者之间的异同点。欢送大家在留言探讨~

2. LinkedBlockingQueue

首先 LinkedBlockingQueue 是一个“可选且有界”的阻塞队列实现,你能够依据须要指定队列的大小。
接下来,我将创立一个LinkedBlockingQueue,它最多能够蕴含 100 个元素:

BlockingQueue<Integer> boundedQueue = new LinkedBlockingQueue<>(100);

当然,咱们也能够通过不指定大小,来创立一个无界的 LinkedBlockingQueue

BlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>();

无界队列示意在创立时未指定队列的大小。因而,队列能够随着元素的增加而动静增长。然而,如果没有残余内存,则队列将抛出 java.lang.OutOfMemory 谬误。

这里留下一个问题给大家思考:创立无界队列是好还是坏呢?

咱们还能够从现有的汇合来创立 LinkedBlockingQueue

Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(listOfNumbers);

LinkedBlockingQueue 实现了 BlockingQueue 接口,该接口为它提供了阻塞性质

阻塞队列示意如果拜访线程已满(当队列有界时)或变为空,则队列将阻塞该线程。如果队列已满,则增加新元素将阻塞拜访线程,除非新元素有可用空间。相似地,如果队列为空,则拜访元素会阻塞调用线程:

ExecutorService executorService = Executors.newFixedThreadPool(1);
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
executorService.submit(() -> {
  try {queue.take();
  } 
  catch (InterruptedException e) {// exception handling}
});

在下面的代码片段中,咱们正在拜访一个空队列。因而,take() 办法阻塞调用线程。

LinkedBlockingQueue 的阻塞个性与一些开销相干。这个代价是因为每个 puttake操作在生产者线程或使用者线程之间都是锁争用的。因而,在许多生产者和消费者的状况下,put和 take 动作可能会慢一些。

3. ConcurrentLinkedQueue

首先申明,ConcurrentLinkedQueue 是一个无边界、线程平安且无阻塞的队列

创立一个空的 ConcurrentLinkedQueue:

ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();

同下面一样,咱们也能够从现有汇合创立 ConcurrentLinkedQueue

Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>(listOfNumbers);

不同于 LinkedBlockingQueueConcurrentLinkedQueue是非阻塞的队列。因而,即便队列为空 (empty),它也不会阻塞线程。相同,它会返回 (null)。尽管它是无界的,但如果没有额定的内存来增加新元素,它依旧会抛出 java.lang.OutOfMemory 谬误。
除了非阻塞之外,ConcurrentLinkedQueue还有其余个性。
在任何生产者 - 消费者场景中,消费者都不会满足于生产者;然而,多个生产者将相互竞争:

int element = 1;
ExecutorService executorService = Executors.newFixedThreadPool(2);
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
 
Runnable offerTask = () -> queue.offer(element);
 
Callable<Integer> pollTask = () -> {while (queue.peek() != null) {return queue.poll().intValue();}
  return null;
};
 
executorService.submit(offerTask);
Future<Integer> returnedElement = executorService.submit(pollTask);
assertThat(returnedElement.get().intValue(), is(equalTo(element)));

第一个工作 offerTask 向队列中增加元素,第二个工作 pollTask 从队列中检索元素。pollTask 首先查看队列中的元素,因为 ConcurrentLinkedQueue 是非阻塞的,并且能够返回 null

4. 求同

LinkedBlockingQueueConcurrentLinkedQueue 都是队列实现,并具备一些独特特色。让咱们讨论一下这两个队列的相似之处:

  1. 实现 Queue 接口
  2. 它们都应用 linked nodes 存储节点
  3. 都实用于并发拜访场景

5. 存异

只管这两种队列都有某些相似之处,但也有一些实质性的特色差别:

个性 LinkedBlockingQueue ConcurrentLinkedQueue
阻塞性 阻塞队列,并实现 blocking queue 接口 非阻塞队列,不实现 blocking queue 接口
队列大小 可选的有界队列,这意味着能够在创立期间定义队列大小 无边界队列,并且没有在创立期间指定队列大小的规定
锁个性 基于锁的队列 无锁队列
算法 锁的实现基于“双锁队列(two lock queue)”算法 依赖于Michael&Scott 算法来实现无阻塞、无锁队列
实现 双锁队列 算法机制中,LinkedBlockingQueue 应用两种不同的锁,putLocktakeLockput/take 操作应用第一个锁类型,take/poll操作应用另一个锁类型 应用 CAS(Compare And Swap)进行操作
阻塞行为 当队列为空时,它会阻塞拜访线程 当队列为空时返回 null,它不会阻塞拜访线程

6. 总结能力提高

首先,咱们别离探讨了这两种队列实现及其一些个性、相似性、以及它们之间的差别。这样的比拟,是否让你对这两种队列有了更粗浅的印象?

关注公众号:锅外的大佬
千河流银的博客

正文完
 0