ImplementationCapacityOrderingBest for
LinkedBlockingQueueOptionally bounded (default: Integer.MAX_VALUE)FIFOGeneral-purpose; high throughput (separate head/tail locks)
ArrayBlockingQueueFixed at constructionFIFOBounded buffers; predictable memory; fair option
SynchronousQueueZero (no internal storage)Direct handoffThread-pool task handoff; rendezvous pattern
PriorityBlockingQueueUnboundedPriority (natural/Comparator)Priority task queues; scheduled work
DelayQueueUnboundedDelay expiryScheduled tasks, retry with backoff, TTL eviction
LinkedTransferQueueUnboundedFIFOHighest throughput; transfer() for synchronous hand-off
OperationThrowsReturns special valueBlocksTimes out
Insertadd(e)offer(e)put(e)offer(e, t, u)
Removeremove()poll()take()poll(t, u)
Examineelement()peek()
import java.util.concurrent.*; BlockingQueue<String> queue = new LinkedBlockingQueue<>(10); // Blocking put — waits if queue is full queue.put("task-1"); // Blocking take — waits if queue is empty String item = queue.take(); // Non-blocking — returns false/null immediately boolean enqueued = queue.offer("task-2"); String polled = queue.poll(); // Timed — waits up to given duration boolean ok = queue.offer("task-3", 500, TimeUnit.MILLISECONDS); String got = queue.poll(1, TimeUnit.SECONDS); // null on timeout
import java.util.concurrent.*; public class ProducerConsumer { private static final String POISON_PILL = "__DONE__"; public static void main(String[] args) throws InterruptedException { BlockingQueue<String> queue = new LinkedBlockingQueue<>(50); int consumerCount = 3; // Producer thread Thread producer = new Thread(() -> { try { for (int i = 1; i <= 20; i++) { queue.put("task-" + i); System.out.println("Produced: task-" + i); } // Send one poison pill per consumer for (int i = 0; i < consumerCount; i++) { queue.put(POISON_PILL); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // Consumer threads ExecutorService consumers = Executors.newFixedThreadPool(consumerCount); for (int i = 0; i < consumerCount; i++) { final int id = i; consumers.submit(() -> { try { while (true) { String task = queue.take(); if (POISON_PILL.equals(task)) break; System.out.println("Consumer " + id + " processing: " + task); Thread.sleep(50); // simulate work } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } producer.start(); producer.join(); consumers.shutdown(); consumers.awaitTermination(10, TimeUnit.SECONDS); } } The poison-pill pattern is the cleanest way to signal consumer shutdown: put one sentinel value per consumer thread. Each consumer exits when it receives the pill, without any shared flag or volatile boolean.

ArrayBlockingQueue uses a single lock for both head and tail operations (unlike LinkedBlockingQueue which uses two). This makes it slightly lower throughput but with an optional fair mode that prevents thread starvation.

// Bounded to 100 elements — rejects (blocks) when full ArrayBlockingQueue<Integer> bounded = new ArrayBlockingQueue<>(100); // Fair mode — longest-waiting thread gets the slot first ArrayBlockingQueue<Integer> fair = new ArrayBlockingQueue<>(100, true); // Drain all elements into a list (non-blocking bulk operation) List<Integer> batch = new ArrayList<>(); int drained = bounded.drainTo(batch, 10); // drain up to 10 items System.out.println("Drained: " + drained + " items");

SynchronousQueue has no internal buffer. A put() blocks until another thread calls take(), and vice versa. It is used internally by Executors.newCachedThreadPool() for immediate task-to-thread hand-off.

import java.util.concurrent.SynchronousQueue; SynchronousQueue<String> handoff = new SynchronousQueue<>(); // Producer — blocks until a consumer is ready Thread producer = new Thread(() -> { try { System.out.println("Producer waiting for consumer..."); handoff.put("Hello!"); // blocks here System.out.println("Handed off"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // Consumer — blocks until producer offers Thread consumer = new Thread(() -> { try { String msg = handoff.take(); // blocks here System.out.println("Received: " + msg); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); producer.start(); Thread.sleep(100); consumer.start();
import java.util.concurrent.PriorityBlockingQueue; record Task(int priority, String name) implements Comparable<Task> { @Override public int compareTo(Task other) { return Integer.compare(other.priority, this.priority); // higher priority first } } PriorityBlockingQueue<Task> pq = new PriorityBlockingQueue<>(); pq.put(new Task(1, "Low priority")); pq.put(new Task(10, "Critical")); pq.put(new Task(5, "Medium")); System.out.println(pq.take().name()); // Critical (priority 10) System.out.println(pq.take().name()); // Medium (priority 5) System.out.println(pq.take().name()); // Low priority (priority 1) PriorityBlockingQueue is unbounded — it never blocks producers. A fast producer and slow consumer can cause unbounded memory growth. Consider using a bounded ArrayBlockingQueue with a custom comparator through a TreeSet-backed wrapper if you need both priority and bounding.

DelayQueue only releases elements whose delay has expired. Elements must implement java.util.concurrent.Delayed.

import java.util.concurrent.*; class DelayedTask implements Delayed { private final String name; private final long runAt; // absolute nanotime DelayedTask(String name, long delayMs) { this.name = name; this.runAt = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(delayMs); } @Override public long getDelay(TimeUnit unit) { return unit.convert(runAt - System.nanoTime(), TimeUnit.NANOSECONDS); } @Override public int compareTo(Delayed other) { return Long.compare( getDelay(TimeUnit.NANOSECONDS), other.getDelay(TimeUnit.NANOSECONDS)); } public String getName() { return name; } } public class DelayQueueExample { public static void main(String[] args) throws InterruptedException { DelayQueue<DelayedTask> queue = new DelayQueue<>(); queue.put(new DelayedTask("task-A", 2000)); // runs after 2 s queue.put(new DelayedTask("task-B", 500)); // runs after 0.5 s queue.put(new DelayedTask("task-C", 1000)); // runs after 1 s while (!queue.isEmpty()) { DelayedTask t = queue.take(); // blocks until next task is ready System.out.println("Running: " + t.getName()); } // Output order: task-B, task-C, task-A } }