Declaration
To use a BlockingQueue in your Java code, you need to declare a variable of type BlockingQueue. Since BlockingQueue is an interface, you can't create an instance of it directly. Instead, you declare a variable of type BlockingQueue & assign it an instance of one of the implementing classes.
Let’s see an example of how you might declare a BlockingQueue:
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
In this example, we declare a BlockingQueue that holds String objects and uses ArrayBlockingQueue as its implementation. The integer 10 specifies the queue's capacity, which is the maximum number of elements it can have at any given time.
You can also choose different types of blocking queues based on your specific requirements, like:
BlockingQueue<Integer> numberQueue = new LinkedBlockingQueue<>();
Here, LinkedBlockingQueue is used without specifying an initial capacity, which means it will have an integer default linked to the maximum integer value unless specified otherwise.
Note: It's important to note that this declaration doesn't actually create a BlockingQueue. It just declares a variable that can hold a reference to a BlockingQueue. To create a BlockingQueue, you need to instantiate one of the implementing classes, which we'll cover in the next section.
Classes that Implement BlockingQueue
As mentioned before, you can't instantiate the BlockingQueue interface directly. Instead, you need to use one of the classes that implement it. Let’s see the main classes that implement BlockingQueue:
- ArrayBlockingQueue: A bounded blocking queue backed by an array. This queue orders elements FIFO (first-in-first-out) and has a fixed capacity. It is a good choice when memory footprint is a concern, and the capacity can be predetermined.
- LinkedBlockingQueue: Potentially unbounded blocking queue backed by linked nodes. Unless the capacity is specified, it is effectively unbounded. It orders elements FIFO and typically has higher throughput than ArrayBlockingQueue but at the expense of higher memory consumption.
- PriorityBlockingQueue: An unbounded blocking queue that uses the same ordering rules as a priority queue, ordering elements according to their natural ordering or by a Comparator provided during queue construction. This queue does not enforce any specific policy for blocking and is suitable for scenarios where processing order is critical.
- DelayQueue: A blocking queue in which an element can only be taken when its delay has expired. It is useful for scheduling tasks that should be executed after a certain period.
- SynchronousQueue: A simple blocking queue where each insert operation must wait for a corresponding remove operation by another thread, and vice versa. This queue has no internal capacity, not even a capacity of one.
- LinkedTransferQueue: This class implements an unbounded TransferQueue based on linked nodes.
The Syntax for Creating Objects
Creating objects of a BlockingQueue means specifying the implementation and initializing it with or without capacity, depending on the constructor. Now, let’s discuss all the steps required to create BlockingQueue objects in Java, which can help ensure correct implementation and initialization:
- Choose the Implementation: Decide which BlockingQueue implementation fits your needs (e.g., ArrayBlockingQueue, LinkedBlockingQueue).
- Specify the Type: Determine the type of elements the queue will handle. This is defined using generics in Java, such as <String> or <Integer>.
- Initialize the Queue: You can initialize the queue with or without a specified capacity. Initialization varies slightly between different implementations.
For ArrayBlockingQueue:
// Initialize with a capacity of 10
BlockingQueue<Integer> intQueue = new ArrayBlockingQueue<>(10);
For LinkedBlockingQueue:
// Initialize without explicit capacity, hence potentially unbounded
BlockingQueue<String> stringQueue = new LinkedBlockingQueue<>();
For PriorityBlockingQueue:
// Initialize without explicit capacity, priorities determined by natural ordering
BlockingQueue<String> priorityQueue = new PriorityBlockingQueue<>();
Note: Each step of this process is crucial for ensuring that your BlockingQueue is set up properly to handle your Java application's concurrency needs. Correct initialization is essential for performance optimization and preventing runtime errors, especially in multi-threaded environments.
BlockingQueue Types
In Java, the various types of BlockingQueue implementations address different operational needs and different situations. Let’s discuss these some common types of blocking queues and their primary uses in a bit of detail:
- ArrayBlockingQueue: This queue is ideal for fixed-size requirements and operates on a first-in-first-out (FIFO) basis. It is a classic 'bounded buffer' in which a fixed-sized array holds elements inserted by producers and extracted by consumers. The capacity must be specified at instantiation and cannot change thereafter. This queue is particularly useful when precise control over resource allocation is necessary.
- LinkedBlockingQueue: It offers the option of either a bounded or unbounded queue. By default, if not specified, its capacity is Integer.MAX_VALUE, making it effectively unbounded. Like ArrayBlockingQueue, it also follows FIFO order. This type is suitable for applications where queue size may vary significantly, providing more flexibility than ArrayBlockingQueue.
- PriorityBlockingQueue: An unbounded queue that prioritizes elements based on their natural ordering or a comparator provided at queue creation. This queue does not block producers and is helpful when processing order is based on certain attributes of the elements rather than just the order in which they were added.
- DelayQueue: A specialized type of blocking queue in which an element can only be removed when its delay has expired. Elements must implement the Delayed interface. This queue is useful for scheduling tasks that should be executed after a given delay.
- SynchronousQueue: A simple mechanism to exchange elements between two threads. Each insert operation must wait for a corresponding remove operation by another thread, and vice versa, making the queue always empty. This type is suitable for handoff designs and is not a typical 'queue' since it does not hold elements beyond the scope of a single exchange.
Basic Operations
BlockingQueue provides four basic operations:
1. Insert (put): This operation adds an element to the queue. If the queue is full (in the case of a bounded queue), the insert operation will block until space becomes available.
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
queue.put("Element 1");
2. Remove (take): This operation removes and returns the head of the queue. If the queue is empty, the remove operation will block until an element becomes available.
String element = queue.take();
3. Insert with timeout (offer): This operation adds an element to the queue if possible. If the queue is full, it will wait for a specified time for space to become available. If space does not become available within the specified time, the operation will fail.
boolean success = queue.offer("Element 2", 10, TimeUnit.SECONDS);
4. Remove with timeout (poll): This operation removes and returns the head of the queue if possible. If the queue is empty, it will wait for a specified time for an element to become available. If an element becomes unavailable within the specified time, the operation will return null.
String element = queue.poll(10, TimeUnit.SECONDS);
Note: These basic operations are designed to allow multiple threads safe, concurrent access to the queue. The blocking nature of these operations ensures that threads will wait when necessary rather than proceeding with invalid states (like trying to remove from an empty queue or insert into a full queue).
Methods of BlockingQueue
In addition to the basic operations, BlockingQueue provides various other methods. These methods are inherited from the interfaces that BlockingQueue extends: Collection, Iterable, and Queue.
Methods declared in interface java.util.Collection
- `add(E e)`: Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions.
- `contains(Object o)`: Returns true if this queue contains the specified element.
- `remove(Object o)`: Removes a single instance of the specified element from this queue if it is present.
- `size()`: Returns the number of elements in this queue.
- ... (there are several more methods in this interface)
Methods declared in interface java.lang.Iterable
- `iterator()`: Returns an iterator over the elements in this queue in proper sequence.
- `forEach(Consumer<? super E> action)`: Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.
Methods declared in interface java.util.Queue
- `element()`: Retrieves, but does not remove, the head of this queue.
- `peek()`: Retrieves, but does not remove, the head of this queue or returns null if this queue is empty.
- `remove()`: Retrieves & removes the head of this queue.
Note: These methods provide additional functionality for interacting with the queue, like checking if an element is present, retrieving elements without removing them, & iterating over the elements in the queue.
The Behavior of BlockingQueue Methods
It's important to understand the behavior of BlockingQueue methods, especially in the context of concurrent programming.
Let’s discuss some of the important points about the behavior of BlockingQueue methods:
1. Blocking Methods:
- `put(E e)`: This method will block if the queue is full until space becomes available.
- `take()`: This method will block if the queue is empty until an element becomes available.
2. Timed Blocking Methods:
- `offer(E e, long timeout, TimeUnit unit)`: This method will block for up to the specified time if the queue is full, trying to add the element. If space becomes unavailable within the specified time, it will return false.
- `poll(long timeout, TimeUnit unit)`: This method will block for up to the specified time if the queue is empty, trying to retrieve an element. If an element becomes unavailable within the specified time, it will return null.
3. Immediate Methods:
- `add(E e)`: This method will immediately return true if it was able to add the element or throw an IllegalStateException if the queue is full.
- `remove()`: This method immediately returns the head of the queue if it is not empty or throws a NoSuchElementException if it is empty.
- `element()`: This method immediately returns the head of the queue if it is not empty or throws a NoSuchElementException if it is empty.
- `offer(E e)`: This method will immediately return true if it was able to add the element or return false if the queue is full.
- `peek()`: This method immediately returns the head of the queue if it is not empty or null if it is empty.
- `poll()`: This method will immediately return the head of the queue if the queue is not empty or return null if the queue is empty.
Note: The blocking methods are useful when you want your thread to wait until the operation can be completed. The timed blocking methods are useful when you want to limit how long your thread waits. The immediate methods are useful when you want to perform an operation only if it can be done immediately.
Frequently Asked Questions
What happens if you try to insert an element into a full BlockingQueue?
If you try to insert an element into a full BlockingQueue using the put() method, the thread will block until space becomes available. If you use the offer() method with a timeout, the thread will block for the specified time & then return false if space doesn't become available.
What's the difference between a bounded & an unbounded BlockingQueue?
A bounded BlockingQueue has a fixed capacity, & once it's full, it will block new insert operations until space becomes available. An unbounded BlockingQueue doesn't have a maximum capacity & will grow as needed to accommodate new elements.
Can you use a BlockingQueue with any data type?
Yes, BlockingQueue is a generic interface, so you can use it with any data type. When you declare a BlockingQueue, you specify the type of elements it will hold within angle brackets, like BlockingQueue<String> or BlockingQueue<MyCustomClass>.
Conclusion
In this article, we discussed BlockingQueue, an essential tool for managing concurrent access to a shared queue in Java. By using blocking operations and supporting multiple implementation classes, BlockingQueue simplifies the development of concurrent applications that need to pass data safely between multiple threads.
You can also check out our other blogs on Code360.