java.util.concurrent.LinkedBlockingQueue Example
In this example we are going to explain the use of the LinkedBlockingQueue
class, and how it is different from the similar ArrayBlockingQueue
. The main point of similarity is the concurrent capabilities of both classes, which makes sense as both are part of the java.util.concurrent
package. Although they are the most common implementations of the BlockingQueue
interface, certain differences need to be taken into consideration when you have to choose one instead of the other.
1. ArrayBlockingQueue vs LinkedBlockingQueue
In a previous article (java.util.concurrent.ArrayBlockingQueue Example), we talked about ArrayBlockingQueue
and its usage. Here, we will try to make some comparisons between ArrayBlockingQueue
and LinkedBlockingQueue
to make clear in which cases we should prefer each one. It is important to make clear distinctions, as both data structures serve very similar needs, but performance and implementation varies.
1.1 Performance
ArrayBlockingQueue
: It uses an internal array in which the elements are kept, and the Queue interface imposes certain rules (like the FIFO rule, which is essential to any queue). Because it uses an array, it has a fixed size which is given in the constructor.LinkedBlocking Queue
: It uses nodes (like a linked list), to keep track of the order of the elements, which increases the complexity of the data structure. It can have a fixed-size limit as well, but if we don’t define one the limit isInteger.MAX_VALUE
by default.
According to the previous information, you can clearly see why ArrayBlockingQueue
is faster than LinkedBlockingQueue
, which is backed by a benchmark that was published in an older JavaCodeGeeks article. The benchmark specifics and results can be found here. In every case, the performance of ArrayBlockingQueue
is better.
1.2 Implementation in synchronization
The major implementation difference between the two data structures (synchronization-wise) is that because ArrayBlockingQueue
keeps the elements in an array it needs only one lock to keep everything synchronized. On the other hand, LinkedBlockingQueue
uses two locks, one for insertion and one for extraction. That happens because while ArrayBlockingQueue
contains just an array, LinkedBlockingQueue
contains a series of connected nodes, so it doesn’t need to keep track of insertion and extraction at the same time.
2. LinkedBlockingQueue Example
Like in our previous example about ArrayBlockingQueue
, we are going to use a Producer-Consumer model in order to check the functionality of our LinkedBlockingQueue
. This time however, we are going to use a system of multiple consumers, to make the distinction more clear. One of the consumers will just look into the data, and the other will remove them. The producer will insert elements as usual.
ArrayBlockingQueueExample.java
import java.util.concurrent.LinkedBlockingQueue; public class LinkedBlockingQueueExample { public static void main(String[] args) { LinkedBlockingQueue queue = new LinkedBlockingQueue(10); Producer producer = new Producer(queue); ObservingConsumer obsConsumer = new ObservingConsumer(queue, producer); RemovingConsumer remConsumer = new RemovingConsumer(queue, producer); Thread producerThread = new Thread(producer); Thread obsConsumerThread = new Thread(obsConsumer); Thread remConsumerThread = new Thread(remConsumer); producerThread.start(); obsConsumerThread.start(); remConsumerThread.start(); } }
Producer.java
import java.util.concurrent.LinkedBlockingQueue; public class Producer implements Runnable { private LinkedBlockingQueue queue; private boolean running; public Producer(LinkedBlockingQueue queue) { this.queue = queue; running = true; } // We need to check if the producer thread is // Still running, and this method will return // the state (running/stopped). public boolean isRunning() { return running; } @Override public void run() { // We are adding elements using put() which waits // until it can actually insert elements if there is // not space in the queue. for (int i = 0; i < 15; i++) { String element = "String" + i; try { queue.put(element); System.out.println("P\tAdding element: " + element); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("P Completed."); running = false; } }
ObservingConsumer.java
import java.util.concurrent.LinkedBlockingQueue; public class ObservingConsumer implements Runnable { private LinkedBlockingQueue queue; private Producer producer; public ObservingConsumer(LinkedBlockingQueue queue, Producer producer) { this.queue = queue; this.producer = producer; } @Override public void run() { // As long as the producer is running, // we want to check for elements. while (producer.isRunning()) { System.out.println("OC\tElements right now: " + queue); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("OC Completed."); System.out.println("Final elements in the queue: " + queue); } }
RemovingConsumer.java
import java.util.concurrent.LinkedBlockingQueue; public class RemovingConsumer implements Runnable { private LinkedBlockingQueue queue; private Producer producer; public RemovingConsumer(LinkedBlockingQueue queue, Producer producer) { this.queue = queue; this.producer = producer; } @Override public void run() { // As long as the producer is running, // we remove elements from the queue. while (producer.isRunning()) { try { System.out.println("RC\tRemoving element: " + queue.take()); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("RC completed."); } }
Output
P Adding element: String0 RC Removing element: String0 OC Elements right now: [] P Adding element: String1 P Adding element: String2 RC Removing element: String1 OC Elements right now: [String2] P Adding element: String3 P Adding element: String4 RC Removing element: String2 OC Elements right now: [String3, String4] P Adding element: String5 RC Removing element: String3 OC Elements right now: [String4, String5] P Adding element: String6 P Adding element: String7 RC Removing element: String4 P Adding element: String8 OC Elements right now: [String5, String6, String7, String8] P Adding element: String9 RC Removing element: String5 OC Elements right now: [String6, String7, String8, String9] P Adding element: String10 P Adding element: String11 RC Removing element: String6 P Adding element: String12 OC Elements right now: [String7, String8, String9, String10, String11, String12] P Adding element: String13 RC Removing element: String7 P Adding element: String14 OC Elements right now: [String8, String9, String10, String11, String12, String13, String14] P Completed. RC completed. OC Completed. Final elements in the queue: [String8, String9, String10, String11, String12, String13, String14]
As you can see, by running 3 threads simultaneously, we took advantage of the concurrency capabilities of LinkedBlockingQueue
completely. The only thing that we had to do is keep it track whether or not the Producer thread was still running, and the rest of the implementation was thread-safe by default. By checking the output you can clearly see the effect of every thread, and the final result (which had less elements than the queue could actually accomodate, because we were removing the older ones in intervals).
3. Download the example
This was an example of LinkedBlockingQueue.
You can download the full source code of this example here : LinkedBlockingQueueExample
I wish all who post code examples would get out of the habit of handling InterruptedException with a stack trace. This is *not* the correct way to handle that exception, but I see it all through production code.
doesn’t it have a possible deadlock if the the consumer consumes the last item and reenter into the (producer.isRunning()) before it gets updates?