A blocking queue extends a queue by adding two sets of methods:
An instance of the BlockingQueue
interface represents a blocking queue.
The BlockingQueue
interface inherits from the Queue
interface.
put()
and offer()
methods adds an element to the tail of blocking queue.
The put() method blocks indefinitely if the blocking queue is full until space becomes available in the queue.
The offer() method lets you specify the time period to wait for space to become available. It returns true if the specified element was added successfully; false otherwise.
take() and poll() methods retrieve and remove the head of blocking queue. take() method blocks indefinitely if the blocking queue is empty. poll() method lets you specify a time period to wait if the blocking queue is empty; it returns null if the specified time elapses before an element became available.
The methods from the Queue
interface within a BlockingQueue
, behave as if you are using a Queue
.
A BlockingQueue
is designed to be thread-safe and can be used
in a producer/consumer-like situation.
A blocking queue does not allow a null element and can be bounded or unbounded.
remainingCapacity()
from BlockingQueue
returns the number of elements that can be added to the blocking queue without blocking.
BlockingQueue
can control the fairness when multiple threads are blocked. If a blocking queue is fair, it can pick the longest waiting thread to perform the operation.
If the blocking queue is not fair, the order to pick is not specified.
The BlockingQueue
interface and all its implementation classes are in the java.util.concurrent
package. The following are the implementation classes for the BlockingQueue
interface:
ArrayBlockingQueue
backed by an array is a bounded implementation class for BlockingQueue
. We can specify the fairness of the blocking queue in its constructor. By default, it is not fair.
LinkedBlockingQueue
can be used as a bounded or unbounded blocking queue. It does not allow specifying a fairness rule for the blocking queue.
PriorityBlockingQueue
is an unbounded implementation class for BlockingQueue
. It works the same way as PriortyQueue
for ordering the elements in the blocking queue and adds the blocking feature to PriorityQueue
.
SynchronousQueue
implements BlockingQueue
and does not have any capacity. The put operation waits for the take operation to get the element. It can do handshake between two threads and exchange an object between two threads. Its isEmpty() method always returns true.
DelayQueue is an unbounded implementation class for BlockingQueue. It keeps an element until a specified delay has passed for that element. If there are more than one elements whose delay has passed, the element whose delay passed earliest will be placed at the head of the queue.
The following code shows how to use blocking queue in a producer/consumer application.
import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /*from w ww .ja v a 2 s .c o m*/ class BQProducer extends Thread { private final BlockingQueue<String> queue; private final String name; public BQProducer(BlockingQueue<String> queue, String name) { this.queue = queue; this.name = name; } @Override public void run() { while (true) { try { this.queue.put(UUID.randomUUID().toString()); Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); break; } } } } class BQConsumer extends Thread { private final BlockingQueue<String> queue; private final String name; public BQConsumer(BlockingQueue<String> queue, String name) { this.queue = queue; this.name = name; } @Override public void run() { while (true) { try { String str = this.queue.take(); System.out.println(name + " took: " + str); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); break; } } } } public class Main { public static void main(String[] args) { int capacity = 5; boolean fair = true; BlockingQueue<String> queue = new ArrayBlockingQueue<>(capacity, fair); new BQProducer(queue, "Producer1").start(); new BQProducer(queue, "Producer2").start(); new BQProducer(queue, "Producer3").start(); new BQConsumer(queue, "Consumer1").start(); new BQConsumer(queue, "Consumer2").start(); } }
The code above generates the following result.
A DelayQueue
implements the BlockingQueue
interface.
The elements inside DelayQueue
must stay for a certain amount of time.
DelayQueue
uses an interface called Delayed
to get the to-be-delayed time.
The interface is in the java.util.concurrent
package.
Its declaration is as follows:
public interface Delayed extends Comparable<Delayed> { long getDelay(TimeUnit timeUnit); }
It extends the Comparable
interface whose compareTo()
method accepts a Delayed object.
The DelayQueue
calls the getDelay()
method of each element to get how long that element must be kept. The DelayQueue
will pass a TimeUnit
to this method.
When the getDelay()
method returns a zero or a negative number, it is time for the element to get out of the queue.
The queue determines which one to pop out by calling the compareTo()
method of the elements. This method determines the priority of an expired element to be removed from the queue.
The following code shows how to use DelayQueue.
import static java.time.temporal.ChronoUnit.MILLIS; import static java.util.concurrent.TimeUnit.MILLISECONDS; /* ww w .ja v a 2 s .c o m*/ import java.time.Instant; import java.util.concurrent.BlockingQueue; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; class DelayedJob implements Delayed { private Instant scheduledTime; String jobName; public DelayedJob(String jobName, Instant scheduledTime) { this.scheduledTime = scheduledTime; this.jobName = jobName; } @Override public long getDelay(TimeUnit unit) { long delay = MILLIS.between(Instant.now(), scheduledTime); long returnValue = unit.convert(delay, MILLISECONDS); return returnValue; } @Override public int compareTo(Delayed job) { long currentJobDelay = this.getDelay(MILLISECONDS); long jobDelay = job.getDelay(MILLISECONDS); int diff = 0; if (currentJobDelay > jobDelay) { diff = 1; } else if (currentJobDelay < jobDelay) { diff = -1; } return diff; } @Override public String toString() { String str = this.jobName + ", " + "Scheduled Time: " + this.scheduledTime; return str; } } public class Main { public static void main(String[] args) throws InterruptedException { BlockingQueue<DelayedJob> queue = new DelayQueue<>(); Instant now = Instant.now(); queue.put(new DelayedJob("A", now.plusSeconds(9))); queue.put(new DelayedJob("B", now.plusSeconds(3))); queue.put(new DelayedJob("C", now.plusSeconds(6))); queue.put(new DelayedJob("D", now.plusSeconds(1))); while (queue.size() > 0) { System.out.println("started..."); DelayedJob job = queue.take(); System.out.println("Job: " + job); } System.out.println("Finished."); } }
The code above generates the following result.
The transfer queue extends the blocking queue.
A producer passes an element to a consumer using the transfer(E element)
method of the TransferQueue
.
When a producer invokes transfer(E element)
method, it waits until a consumer takes its element.
The tryTransfer()
method provides a non-blocking and a timeout version of the method.
The getWaitingConsumerCount()
method returns the number of waiting consumers.
The hasWaitingConsumer()
method returns true if there is a waiting consumer; otherwise, it returns false.
The LinkedTransferQueue
is an implementation class for the TransferQueue
interface. It provides an unbounded TransferQueue
.
The following code shows how to use the TransferQueue
.
import java.util.concurrent.LinkedTransferQueue; import java.util.concurrent.TransferQueue; import java.util.concurrent.atomic.AtomicInteger; /* www. jav a 2s .c o m*/ class TQProducer extends Thread { private String name; private TransferQueue<Integer> tQueue; private AtomicInteger sequence; public TQProducer(String name, TransferQueue<Integer> tQueue, AtomicInteger sequence) { this.name = name; this.tQueue = tQueue; this.sequence = sequence; } @Override public void run() { while (true) { try { Thread.sleep(4000); int nextNum = this.sequence.incrementAndGet(); if (nextNum % 2 == 0) { System.out.format("%s: Enqueuing: %d%n", name, nextNum); tQueue.put(nextNum); // Enqueue } else { System.out.format("%s: Handing off: %d%n", name, nextNum); System.out.format("%s: has a waiting consumer: %b%n", name, tQueue.hasWaitingConsumer()); tQueue.transfer(nextNum); // A hand off } } catch (InterruptedException e) { e.printStackTrace(); } } } } class TQConsumer extends Thread { private final String name; private final TransferQueue<Integer> tQueue; public TQConsumer(String name, TransferQueue<Integer> tQueue) { this.name = name; this.tQueue = tQueue; } @Override public void run() { while (true) { try { Thread.sleep(3000); int item = tQueue.take(); System.out.format("%s removed: %d%n", name, item); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Main { public static void main(String[] args) { final TransferQueue<Integer> tQueue = new LinkedTransferQueue<>(); final AtomicInteger sequence = new AtomicInteger(); for (int i = 0; i < 5; i++) { try { tQueue.put(sequence.incrementAndGet()); System.out.println("Initial queue: " + tQueue); new TQProducer("Producer-1", tQueue, sequence).start(); new TQConsumer("Consumer-1", tQueue).start(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
The code above generates the following result.