LinkedBlockingQueue vs ConcurrentLinkedQueue – LinkedBlockingQueue vs ConcurrentLinkedQueue

最后修改: 2020年 5月 20日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

LinkedBlockingQueue and ConcurrentLinkedQueue are the two most frequently used concurrent queues in Java. Although both queues are often used as a concurrent data structure, there are subtle characteristics and behavioral differences between them.

LinkedBlockingQueueConcurrentLinkedQueue是Java中最常用的两种并发队列。虽然这两个队列都经常被用作并发数据结构,但它们之间存在着微妙的特征和行为差异。

In this short tutorial, we’ll discuss both of these queues and explain their similarities and differences.

在这个简短的教程中,我们将讨论这两个队列,并解释它们的相似之处和区别。

2. LinkedBlockingQueue

2.LinkedBlockingQueue

The LinkedBlockingQueue is an optionally-bounded blocking queue implementation, meaning that the queue size can be specified if needed.

LinkedBlockingQueue是一个可选的有边界的阻塞队列实现,这意味着如果需要,可以指定队列大小。

Let’s create a LinkedBlockingQueue which can contain up to 100 elements:

让我们创建一个LinkedBlockingQueue,它最多可以包含100个元素。

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

We can also create an unbounded LinkedBlockingQueue just by not specifying the size:

我们也可以创建一个无界的LinkedBlockingQueue,只是不指定大小。

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

An unbounded queue implies that the size of the queue is not specified while creating. Therefore, the queue can grow dynamically as elements are added to it. However, if there is no memory left, then the queue throws a java.lang.OutOfMemoryError.

无界队列意味着队列的大小在创建时没有被指定。因此,队列可以随着元素的添加而动态增长。然而,如果没有剩余的内存,那么队列会抛出一个java.lang.OutOfMemoryError.

We can create a LinkedBlockingQueue from an existing collection as well:

我们也可以从一个现有的集合中创建一个LinkedBlockingQueue

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

The LinkedBlockingQueue class implements the BlockingQueue interface, which provides the blocking nature to it.

LinkedBlockingQueue实现了BlockingQueue接口,为其提供了阻塞的特性

A blocking queue indicates that the queue blocks the accessing thread if it is full (when the queue is bounded) or becomes empty. If the queue is full, then adding a new element will block the accessing thread unless there is space available for the new element. Similarly, if the queue is empty, then accessing an element blocks the calling thread:

阻塞队列表明,如果队列满了(当队列有界时)或变成空了,队列会阻塞访问线程。如果队列是满的,那么添加一个新的元素将阻塞访问线程,除非有可用的空间给新元素。同样地,如果队列是空的,那么访问一个元素就会阻塞调用线程。

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

In the above code snippet, we are accessing an empty queue. Therefore, the take method blocks the calling thread.

在上面的代码片段中,我们正在访问一个空队列。因此,take方法阻塞了调用线程。

The blocking feature of the LinkedBlockingQueue is associated with some cost. This cost is because every put or the take operation is lock contended between the producer or the consumer threads. Therefore, in scenarios with many producers and consumers, put and take actions could be slower.

LinkedBlockingQueen的阻塞功能与一些成本相关。这个代价是因为每个puttake操作都是在生产者或消费者线程之间争夺锁。因此,在有许多生产者和消费者的场景中,put和take操作可能会比较慢。

3. ConcurrentLinkedQueue

3.ConcurrentLinkedQueue

A ConcurrentLinkedQueue is an unbounded, thread-safe, and non-blocking queue.

ConcurrentLinkedQueue 是一个无界的、线程安全的、非阻塞的队列。

Let’s create an empty ConcurrentLinkedQueue:

让我们创建一个空的ConcurrentLinkedQueue

ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();

We can create a ConcurrentLinkedQueue from an existing collection as well:

我们也可以从一个现有的集合中创建一个ConcurrentLinkedQueue

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

Unlike a LinkedBlockingQueue, a ConcurrentLinkedQueue is a non-blocking queue. Thus, it does not block a thread once the queue is empty. Instead, it returns null. Since its unbounded, it’ll throw a java.lang.OutOfMemoryError if there’s no extra memory to add new elements.

LinkedBlockingQueue不同,ConcurrentLinkedQueue是一个非阻塞队列。因此,一旦队列为空,它就不会阻塞线程。相反,它会返回null。由于它是无界的,如果没有多余的内存来添加新的元素,它将抛出一个java.lang.OutOfMemoryError

Apart from being non-blocking, a ConcurrentLinkedQueue has additional functionality.

除了非阻塞之外,ConcurrentLinkedQueue还有其他功能。

In any producer-consumer scenario, consumers will not contend with producers; however, multiple producers will contend with one another:

在任何生产者-消费者的情况下,消费者不会与生产者争吵;但是,多个生产者会相互争吵。

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)));

The first task, offerTask, adds an element to the queue, and the second task, pollTask, retrieve an element from the queue. The poll task additionally checks the queue for an element first as ConcurrentLinkedQueue is non-blocking and can return a null value.

第一个任务,offerTask,向队列添加一个元素,第二个任务,pollTask,从队列中检索一个元素。轮询任务还先检查队列中的元素,因为ConcurrentLinkedQueue是非阻塞的,可以返回一个null

4. Similarities

4.相似性

Both LinkedBlockingQueue and the ConcurrentLinkedQueue are queue implementations and share some common characteristics. Let’s discuss the similarities of these two queues:

LinkedBlockingQueueConcurrentLinkedQueue都是队列的实现,并且有一些共同的特征。让我们来讨论这两个队列的相似之处。

  1. Both implements the Queue Interface
  2. They both use linked nodes to store their elements
  3. Both are suitable for concurrent access scenarios

5. Differences

5.差异

Although both of these queues have certain similarities, there are substantial characteristics differences, too:

虽然这两个队列有某些相似之处,但也有实质性的特征差异。

Feature LinkedBlockingQueue ConcurrentLinkedQueue
Blocking Nature It is a blocking queue and implements the BlockingQueue interface It is a non-blocking queue and does not implement the BlockingQueue interface
Queue Size It is an optionally bounded queue, which means there are provisions to define the queue size during creation It is an unbounded queue, and there is no provision to specify the queue size during creation
Locking Nature It is a lock-based queue It is a lock-free queue
Algorithm It implements its locking based on two-lock queue algorithm It relies on the Michael & Scott algorithm for non-blocking, lock-free queues
Implementation In the two-lock queue algorithm mechanism, LinkedBlockingQueue uses two different locks – the putLock and the takeLock. The put/take operations uses the first lock type, and the take/poll operations use the other lock type It uses CAS (Compare-And-Swap) for its operations
Blocking Behavior It is a blocking queue. So, it blocks the accessing threads when the queue is empty It does not block the accessing thread when the queue is empty and returns null

6. Conclusion

6.结语

In this article, we learned about LinkedBlockingQueue and ConcurrentLinkedQueue.

在这篇文章中,我们了解了LinkedBlockingQueueConcurrentLinkedQueue

First, we individually discussed these two queue implementations and some of their characteristics. Then, we saw the similarities between these two queue implementations. Finally, we explored the differences between these two queue implementations.

首先,我们单独讨论了这两个队列的实现和它们的一些特点。然后,我们看到了这两个队列实现之间的相似之处。最后,我们探讨了这两个队列实现之间的差异。

As always, the source code of the examples is available over on GitHub.

像往常一样,这些例子的源代码可以在GitHub上找到over