LinkedBlockingQueue

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 is Integer.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.

Download
You can download the full source code of this example here : LinkedBlockingQueueExample

Ilias Koutsakis

Ilias has graduated from the Department of Informatics and Telecommunications of the National and Kapodistrian University of Athens. He is interested in all aspects of software engineering, particularly data mining, and loves the challenge of working with new technologies. He is pursuing the dream of clean and readable code on a daily basis.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Kelly
Kelly
7 years ago

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.

Carmelo
Carmelo
5 years ago

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?

Back to top button