Java > Java Collections Framework > Queue and Deque > BlockingQueue and ConcurrentQueue

ConcurrentLinkedQueue Example

This example demonstrates the use of ConcurrentLinkedQueue, a non-blocking, thread-safe queue. Unlike BlockingQueue, it doesn't block threads when the queue is empty or full. It is useful for scenarios where you want to avoid blocking and need high concurrency.

Code Implementation

This code demonstrates a producer-consumer scenario using ConcurrentLinkedQueue. The producer1 thread adds messages to the queue using queue.offer(), and the consumer1 thread retrieves messages using queue.poll(). The poll() method returns null if the queue is empty, so the consumer checks for null before processing. This demonstrates the non-blocking nature of ConcurrentLinkedQueue.

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

public class ConcurrentLinkedQueueExample {

    public static void main(String[] args) throws InterruptedException {
        Queue<String> queue = new ConcurrentLinkedQueue<>();

        Thread producer1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                queue.offer("Message-" + i);
                System.out.println("Produced: Message-" + i);
                try {
                    Thread.sleep(100); // Simulate production time
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });

        Thread consumer1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                String message = queue.poll();
                if (message != null) {
                    System.out.println("Consumed: " + message);
                } else {
                    System.out.println("Queue is empty.");
                }
                try {
                    Thread.sleep(200); // Simulate consumption time
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });

        producer1.start();
        consumer1.start();

        producer1.join();
        consumer1.join();

        System.out.println("Queue size: " + queue.size());
    }
}

Concepts Behind the Snippet

  • Non-Blocking Queue: A queue that does not block threads when the queue is empty or full.
  • Concurrent Linked Queue: An unbounded thread-safe queue based on linked nodes.
  • CAS (Compare and Swap): Internally uses CAS operations to ensure thread safety without explicit locking.

Real-Life Use Case

ConcurrentLinkedQueue is suitable for high-throughput scenarios where blocking is unacceptable. Examples include event processing systems, asynchronous logging, and data ingestion pipelines where the rate of production and consumption fluctuates significantly. It's good for multi-threaded application like a chat application where a server receives messages and broadcasts them in real-time.

Best Practices

  • Handle Empty Queues: Since poll() returns null when the queue is empty, ensure your consumer handles this case appropriately.
  • Consider Looping: In high-contention scenarios, consider using a loop with a short delay to avoid spinning excessively when the queue is empty.
  • Monitor Queue Size: Since it is unbounded, monitor the queue size to prevent excessive memory consumption.

Interview Tip

Understand the difference between blocking and non-blocking queues and when to use each. Be prepared to discuss the trade-offs between ConcurrentLinkedQueue and BlockingQueue implementations.

When to Use Them

Use ConcurrentLinkedQueue when you need a thread-safe queue that doesn't block and high concurrency is critical. It's suitable when producers and consumers operate independently and blocking would negatively impact performance.

Memory Footprint

ConcurrentLinkedQueue is unbounded, so its memory footprint grows dynamically as elements are added. Monitor the queue size to prevent excessive memory consumption, especially in long-running applications.

Alternatives

  • BlockingQueue: If blocking behavior is acceptable and simplifies concurrency management.
  • CopyOnWriteArrayList: For read-heavy scenarios where writes are infrequent, provides thread safety by creating a new copy of the list on each write.

Pros

  • Non-Blocking: Avoids thread blocking, maximizing concurrency.
  • Thread Safe: Built-in thread safety without explicit locking.
  • High Throughput: Can achieve high throughput in highly concurrent environments.

Cons

  • Potential for Spin-Locking: Consumers might spin while waiting for elements to be added to the queue.
  • Unbounded: Unbounded size can lead to memory exhaustion if not carefully monitored.
  • Requires Handling Empty Queue: Requires explicit handling of empty queue conditions.

FAQ

  • What happens if I try to poll from an empty ConcurrentLinkedQueue?

    The poll() method returns null.
  • Is ConcurrentLinkedQueue suitable for all concurrent scenarios?

    No. It's best suited for scenarios where blocking is undesirable. If blocking is acceptable and simplifies your code, a BlockingQueue might be a better choice.
  • How does ConcurrentLinkedQueue ensure thread safety?

    It uses lock-free algorithms based on Compare-and-Swap (CAS) operations. This avoids the overhead of explicit locking.