Thursday, 23 September 2010

Queues in Java 5: the Queue interface

Java 5 adds queues to the Collections framework. A queue is in some ways a sub-construct of a list, usually implemented as a linked list, with the following characteristics:
  • unlike a normal list, it is not random access: that is, you can't get or set the element at an arbitrary position in the list;
  • get and set operations always take place at the ends of the queue (generally, one "takes from the head" and "puts on the tail").
In a standard queue, the next item to be taken is the one at the head of the queue, i.e. the one that has been there the longest. When items are added to the queue, they are added to the tail. The item at the tail can only be accessed once all the items placed before it have been removed in order.

Why use a queue?

So you may well be thinking: why use a queue if it has these restrictions? Can't you just use a boring old ArrayList or LinkedList1? It turns out there are at least three main reasons:
  • in some cases, a queue is "conceptually what we want";
  • eliminating random access allows optimisations for concurrency;
  • Java's efficient BlockingQueue implementations can take some of the work out of the most typical use of queues.
Places where we "conceptually" want a queue are where we are dealing with a so-called producer-consumer pattern. That is, one thread "produces" a list of jobs for another thread to pick up. Of course, we could use an ordinary (synchronized) LinkedList if this was purely our motivation. However, it turns out that restricting access to the head and tail of the queue allows for further optimisation for concurrent access.

Queues with thread pools

One place, then, in which queues are useful is for the work queue of a thread pool. Java provides the ThreadPoolExecutor class; when constructing this class, you can pass in the queue that you want the thread pool to use. Or, you can construct your thread pool with one of the utility methods in the Executors class, in which case a default BlockingQueue will be used.

Queue implementations firstly share a new Queue interface, which has several methods for accessing the head and tail of the queue. Recall that items are always placed on the end or "tail" of the list, and always read from the beginning or "head" of the list.
OperationThrows exception
if not possible
Returns value
if not possible
Add item to tailadd()offer()
Remove item from headremove()poll()
"Peek" item at headelement()peek()
Methods specified by the Java Queue interface

Types of Queues

Java provides Queue implementations depending on a few key criteria:
  • thread-safety: if you don't require the queue to be accessed concurrently from multiple threads, then a plain LinkedList can be used as a Queue; the advantage of the other implementations is that they offer efficient thread-safety;
  • blocking or non-blocking: various blocking implementations add extra methods to put and remove items from the queue, blocking until the operation is possible, with an optional time limit;
  • bound or non-bound: sometimes it is useful to put an upper limit on the number of items that can fit in the queue, e.g. to prevent a thread pool from queueing up too many jobs when the machine is busy;
  • other special operations: Java provides an implementation that orders by priority, and another that applies a delay to queued items.
As of Java 6, the various queue classes are as follows:
Queue implementations as of Java 6
Blocking?Other criteriaBoundNon-bound
BlockingNoneArrayBlockingQueueLinkedBlockingQueue
Priority-based PriorityBlockingQueue
Delayed DelayQueue
Non-blockingThread-safe ConcurrentLinkedQueue
Non thread-safe LinkedList
Non thread-safe, priority-based PriorityQueue
One further type of queue not included above is the SynchronousQueue, which is effectively a zero-length queue (so that a thread adding an item to the queue will block until another thread removes the item).

Blocking queues

In general, the most interesting queue implementations are the various blocking queues, which allow efficient concurrent access and are useful for coordinating objects between threads, particularly in the so-called producer-consumer pattern. In Java, all blocking queue implementations implement the BlockingQueue interface, which we look at next.

No comments:

Post a Comment