Java
Concurrency
Queue
LinkedBlockingQueue
ConcurrentLinkedQueue

LinkedBlockingQueue vs ConcurrentLinkedQueue

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

When dealing with concurrency in Java, efficient and thread-safe communication between threads is crucial. Two popular classes assisting this communication are LinkedBlockingQueue and ConcurrentLinkedQueue. Both are part of the Java Collections Framework and are designed for different use cases. This article explores both classes in detail, highlighting their features, usages, differences, and use cases.

LinkedBlockingQueue

Overview

LinkedBlockingQueue is part of the Java’s BlockingQueue interface, which provides thread-safe operations for adding, removing, and inspecting elements. The principal feature of BlockingQueues is that they block threads attempting to add or remove elements when the condition isn't met, such as the queue being full or empty.

Key Characteristics

  • Bounded Capacity: LinkedBlockingQueue can have an upper bound limit, making it useful when you want to prevent resource exhaustion.
  • Thread Safety: It uses separate locks for put and take operations to achieve thread safety, reducing contention between producer and consumer threads.
  • Blocking Operations: It includes blocking operations such as put(), take(), and non-blocking alternatives like offer(), poll(), peek().
  • Use Cases: Ideal for producer-consumer problems where you have a fixed upper bound.

Example

java
1LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);
2
3// Producer thread
4new Thread(() -> {
5    try {
6        queue.put(1); // Blocks if the queue is full
7    } catch (InterruptedException e) {
8        e.printStackTrace();
9    }
10}).start();
11
12// Consumer thread
13new Thread(() -> {
14    try {
15        Integer item = queue.take(); // Blocks if the queue is empty
16        System.out.println("Consumed: " + item);
17    } catch (InterruptedException e) {
18        e.printStackTrace();
19    }
20}).start();

ConcurrentLinkedQueue

Overview

ConcurrentLinkedQueue implements the Queue interface, offering an unbounded, lock-free, thread-safe queue for high-concurrency environments. It is based on the Michael and Scott algorithm, using a Compare-And-Swap (CAS) for operations, yielding better performance in multi-threaded scenarios.

Key Characteristics

  • Unbounded Capacity: The queue size grows dynamically, hence no need to specify a capacity.
  • Lock-Free: Implemented using a non-blocking CAS algorithm that minimizes latency.
  • Thread Safety: Ensures thread safety with a low synchronization cost.
  • Non-blocking Operations: Only non-blocking operations like offer(), poll(), and peek() are available. There are no blocking variants.
  • Use Cases: Well-suited for highly concurrent scenarios like work-stealing queues, task scheduling.

Example

java
1ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
2
3// Producer thread
4new Thread(() -> {
5    queue.offer(1); // Immediately returns after adding the item
6}).start();
7
8// Consumer thread
9new Thread(() -> {
10    Integer item = queue.poll(); // Immediately returns, null if queue is empty
11    if (item != null) {
12        System.out.println("Consumed: " + item);
13    }
14}).start();

Performance Comparison

The performance comparison between these two queues is task-dependent. Here's a high-level comparison:

FeatureLinkedBlockingQueueConcurrentLinkedQueue
CapacityBounded (can be unbounded)Unbounded
Concurrency MechanismUses separate locks for put and takeUses lock-free CAS operations
Blocking SupportYes (offers blocking and timeout methods)No (completely non-blocking)
Thread SafetyHigh (suitable for producer-consumer)High (suitable for highly concurrent)
ComplexityModerate (overheads due to locks)Low (due to lock-free operations)
PerformanceModerate (higher thread contention)High (better scaling with more threads)

Decision Factors

When to Use LinkedBlockingQueue

  • Producer-Consumer Patterns: Especially when the producer needs to wait for the consumer to free up space or the consumer needs to wait for the producer.
  • Backpressure Handling: If you need to have a mechanism to handle backpressure by implementing bounded queues.

When to Use ConcurrentLinkedQueue

  • High-Concurrency Environment: If you require a queue that scales well under high-concurrency without the overhead of locks.
  • Low-Latency Requirements: When you want operations to complete as fast as possible due to usage of CAS.

Conclusion

In summation, both LinkedBlockingQueue and ConcurrentLinkedQueue provide robust mechanisms for threading needs but are optimized for different scenarios. LinkedBlockingQueue is ideal for scenarios where blocking is beneficial, and ConcurrentLinkedQueue excels in high-throughput, low-latency operations. Understanding the intricacies of your use case will guide you in choosing the appropriate queue type for your project.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.