Java ArrayBlockingQueue


In multi-threaded programming, managing shared resources efficiently is a common challenge. Java's ArrayBlockingQueue class is a useful tool for managing a thread-safe queue with a fixed capacity. It is part of the java.util.concurrent package and implements the BlockingQueue interface, which provides blocking operations for inserting and removing elements from the queue.

An ArrayBlockingQueue is particularly useful in scenarios where you need to control the flow of elements between threads (for example, in producer-consumer patterns) while ensuring that the queue doesn't exceed a set limit. It provides bounded queues, meaning there is a predefined capacity, and when full, the producer threads are blocked until space becomes available.


What is an ArrayBlockingQueue in Java?

The ArrayBlockingQueue class in Java is a type of BlockingQueue that is backed by an array, offering a fixed-size queue for elements. The size is specified when the queue is created, and once the queue reaches its capacity, attempts to add elements will cause the producer thread to block until space is available. Similarly, when the queue is empty, consumer threads attempting to remove elements will block until data becomes available.

Key features:

  • Bounded Capacity: The queue has a fixed capacity that is set when it is created.
  • Thread-Safety: All operations on the ArrayBlockingQueue are atomic and thread-safe.
  • Blocking Operations: It provides blocking put() and take() operations, which allow for safe data exchange between threads.
  • Fairness Option: The queue can be created in a fair mode, ensuring that threads acquire elements in the order they requested them (first-come-first-served).

Constructor of ArrayBlockingQueue

The constructor for ArrayBlockingQueue requires a capacity value. You can also specify a fairness policy.

ArrayBlockingQueue(int capacity)
ArrayBlockingQueue(int capacity, boolean fair)
  • capacity: The fixed size of the queue.
  • fair (optional): If true, threads will be granted access to the queue in the order they requested it (FIFO). If false, the queue will not guarantee any particular order.

Key Methods in ArrayBlockingQueue

Here are some of the common methods provided by the ArrayBlockingQueue class:

  • put(E e): Adds the specified element to the queue, blocking until space is available.
  • take(): Retrieves and removes the head of the queue, blocking if necessary until an element becomes available.
  • offer(E e): Inserts the specified element into the queue if possible, returning false if the queue is full.
  • poll(): Retrieves and removes the head of the queue, returning null if the queue is empty.
  • offer(E e, long timeout, TimeUnit unit): Attempts to insert the specified element into the queue, blocking for a given time if necessary.
  • poll(long timeout, TimeUnit unit): Retrieves and removes the head of the queue, waiting for the specified time if necessary.
  • remainingCapacity(): Returns the number of available spaces in the queue.
  • clear(): Removes all elements from the queue.

Example 1: Basic Usage of ArrayBlockingQueue

Here’s a simple example demonstrating how to use an ArrayBlockingQueue with a producer-consumer scenario.

import java.util.concurrent.ArrayBlockingQueue;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // Create an ArrayBlockingQueue with a capacity of 2
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);

        // Producer thread
        Thread producer = new Thread(() -> {
            try {
                // Add items to the queue
                System.out.println("Producer: Adding item 1");
                queue.put("Item 1");
                System.out.println("Producer: Adding item 2");
                queue.put("Item 2");
                System.out.println("Producer: Adding item 3");
                queue.put("Item 3");  // This will block as the queue is full
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // Consumer thread
        Thread consumer = new Thread(() -> {
            try {
                // Remove items from the queue
                System.out.println("Consumer: Removing " + queue.take());
                System.out.println("Consumer: Removing " + queue.take());
                System.out.println("Consumer: Removing " + queue.take());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        producer.start();
        consumer.start();

        producer.join();
        consumer.join();
    }
}

Output:

Producer: Adding item 1
Producer: Adding item 2
Consumer: Removing Item 1
Producer: Adding item 3
Consumer: Removing Item 2
Consumer: Removing Item 3

In this example:

  • The producer thread adds items to the queue. When it tries to add a third item, it will block because the queue's capacity is limited to 2.
  • The consumer thread removes items from the queue, causing the producer to be unblocked once space becomes available.

Example 2: Using Fair Mode with ArrayBlockingQueue

You can use the fairness option when creating an ArrayBlockingQueue. This ensures that threads are granted access to the queue in the order they requested it (FIFO).

import java.util.concurrent.ArrayBlockingQueue;

public class FairQueueExample {
    public static void main(String[] args) throws InterruptedException {
        // Create a fair ArrayBlockingQueue with a capacity of 2
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2, true);

        // Producer thread
        Thread producer = new Thread(() -> {
            try {
                System.out.println("Producer: Adding item 1");
                queue.put("Item 1");
                System.out.println("Producer: Adding item 2");
                queue.put("Item 2");
                System.out.println("Producer: Adding item 3");
                queue.put("Item 3");  // This will block as the queue is full
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        // Consumer thread
        Thread consumer1 = new Thread(() -> {
            try {
                System.out.println("Consumer 1: Removing " + queue.take());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread consumer2 = new Thread(() -> {
            try {
                System.out.println("Consumer 2: Removing " + queue.take());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        consumer1.start();
        consumer2.start();
        producer.start();

        producer.join();
        consumer1.join();
        consumer2.join();
    }
}

Output:

Producer: Adding item 1
Producer: Adding item 2
Consumer 1: Removing Item 1
Producer: Adding item 3
Consumer 2: Removing Item 2

In this example, even if two consumer threads are waiting, the fair mode ensures that they are served in the order they were created.


When to Use ArrayBlockingQueue

The ArrayBlockingQueue is ideal for the following use cases:

  1. Producer-Consumer Problem: When you need to have a fixed capacity queue between threads where producers can add data, and consumers can consume data without overflowing.
  2. Thread Coordination: When multiple threads need to wait for resources (elements) to become available, and you want to avoid wasting CPU resources by busy waiting.
  3. Managing Fixed-Size Buffers: It’s useful for implementing systems that require a fixed buffer size, such as network packet handling or task scheduling.
  4. Rate Limiting: Limiting the rate at which items can be added to or removed from the queue to avoid overloading.