1. A simple opening

LinkedBlockingQueue and ConcurrentLinkedQueue are the most commonly used queues in Java high-concurrency scenarios. Although the two queues are often used as data structures in concurrent scenarios, there are subtle characteristics and behavior differences between them. In this article, I will discuss the similarities and differences between the two. Welcome to comment ~

2. LinkedBlockingQueue

First LinkedBlockingQueue is an “optional and bounded” implementation of a blocking queue that you can specify as large as you want. Next, I’ll create a LinkedBlockingQueue that can contain up to 100 elements:

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

Of course, we can also create an unbounded LinkedBlockingQueue by not specifying the size:

BlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>();
Copy the code

An unbounded queue means that the size of the queue was not specified when it was created. Therefore, queues can grow dynamically as elements are added. However, if there is no memory left, the queue throws a java.lang.outofMemory error.

Here’s a question for you to ponder: Is creating unbounded queues good or bad?

We can also create a LinkedBlockingQueue from an existing collection:

Collection<Integer> listOfNumbers = Arrays.asList(1.2.3.4.5);
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(listOfNumbers);
Copy the code

LinkedBlockingQueue implements the BlockingQueue interface, which provides it with blocking properties.

A blocking queue indicates that the queue will block the access thread if it is full (when the queue is bounded) or becomes empty. If the queue is full, adding a new element blocks the access thread unless there is space available for the new element. Similarly, if the queue is empty, accessing the 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}});Copy the code

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

The blocking nature of LinkedBlockingQueue is associated with some overhead. This cost is due to the fact that every PUT or take operation is a lock contention between producer or consumer threads. Therefore, put and take actions may be slower in the case of many producers and consumers.

3. ConcurrentLinkedQueue

First, ConcurrentLinkedQueue is a borderless, thread-safe, and non-blocking queue

Create an empty ConcurrentLinkedQueue:

ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
Copy the code

As above, we can also create ConcurrentLinkedQueue from an existing collection:

Collection<Integer> listOfNumbers = Arrays.asList(1.2.3.4.5);
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>(listOfNumbers);
Copy the code

Unlike LinkedBlockingQueue, ConcurrentLinkedQueue is a non-blocking queue. Therefore, even if the queue is empty, it does not block the thread. Instead, it returns null. Although it is unbounded, it still throws a java.lang.outofMemory error if there is no extra memory to add new elements. In addition to being non-blocking, ConcurrentLinkedQueue has other features. In any producer-consumer scenario, consumers are not satisfied with producers; However, multiple producers will compete with each other:

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)));
Copy the code

The first task, offerTask, adds elements to the queue, and the second task, pollTask, retrieves elements from the queue. PollTask first checks the elements in the queue because ConcurrentLinkedQueue is non-blocking and can return null values.

4. Seek common

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

  1. Both implement the Queue interface
  2. They all use Linked Nodes storage nodes
  3. Both are suitable for concurrent access scenarios

5. Ground while putting aside differences

Although both cohorts share certain similarities, there are also some substantial characteristic differences:

features LinkedBlockingQueue ConcurrentLinkedQueue
obstructive Block the queue and implementblocking queueinterface Non-blocking queue, not implementedblocking queueinterface
Queue size Optional bounded queues, which means queue sizes can be defined during creation Borderless queues, and there is no requirement to specify queue size during creation
The lock feature Lock-based queues Lock-free queue
algorithm The implementation of the lock is based on the “two Lock queue” algorithm Depends on theMichael&Scott algorithm to achieve no blocking, no lock queue
implementation inDouble lock queueIn the algorithm mechanism,LinkedBlockingQueueUsing two different locks,putLockandtakeLock.put/takeThe operation uses the first lock type,take/pollThe operation uses another lock type Use the CAS (Compare And Swap) to operate
Blocking behavior When the queue is empty, it blocks access threads Null is returned when the queue is empty, which does not block the accessing thread

6. Summary leads to progress

First, we discussed both queue implementations and some of their features, similarities, and differences. Does that give you a better impression of the two cohorts?

Concern public number: the big guy outside the pot qian River silver’s blog