What is a BlockingQueue, and how do you use it for the producer–consumer problem?

A BlockingQueue is a thread-safe queue whose put() waits when the queue is full and whose take() waits when it is empty. That handles all the waiting and signalling of the producer–consumer pattern for you, without wait() and notify(). The common implementations differ in capacity and ordering.

Key Points: • ArrayBlockingQueue: bounded, array-based, one lock. Good default when you want back-pressure. • LinkedBlockingQueue: optionally bounded (unbounded by default), with separate locks for put and take, so producers and consumers contend less. • PriorityBlockingQueue: unbounded and ordered by priority. • SynchronousQueue: no capacity; each put waits for a take (a direct hand-off). Executors.newCachedThreadPool() uses it. • DelayQueue: elements become available only after their delay expires. • Method families: put/take block, offer/poll return immediately (or wait with a timeout), add/remove throw exceptions.

Code Example:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ProducerConsumer {

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);   // small buffer
        int poisonPill = -1;

        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    queue.put(i);                 // waits when the queue is full
                }
                queue.put(poisonPill);            // tell the consumer to stop
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                int sum = 0;
                while (true) {
                    int item = queue.take();      // waits when the queue is empty
                    if (item == poisonPill) break;
                    sum += item;
                }
                System.out.println("Consumer finished, sum = " + sum);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();
        producer.join();
        consumer.join();
        System.out.println("Queue empty at the end: " + queue.isEmpty());
    }
}

Output:

Consumer finished, sum = 15 Queue empty at the end: true

Good to Know: The "poison pill" (a special value that tells the consumer to stop) is a standard way to shut down consumers cleanly. Interviewers like it, because it shows you have thought about how the threads end, not only how they start.

Interview Tip: A concise interview answer is:

"A BlockingQueue is a thread-safe queue where put waits while it's full and take waits while it's empty, so it solves producer–consumer without wait and notify. I usually use a bounded ArrayBlockingQueue or LinkedBlockingQueue for back-pressure; SynchronousQueue hands items over directly, and PriorityBlockingQueue and DelayQueue add ordering and delays."

Previous question: What is CopyOnWriteArrayList, and when should you use it?

Next question: What is WeakHashMap, and when would you use it?

More Java Collections interview questions

All Java Collections questions