Your system needs to wait for input and act on an object the moment it is added to
a Buffer
. To achieve this, you need
your application to block until input is received.
Use BlockingBuffer
to decorate
an instance of Buffer
. When a process
calls get( )
or remove( )
on a buffer decorated with BlockingBuffer
, the decorated buffer does not
return a value until it has an object to return. The following example
creates a BlockingBuffer
and a
listener that calls remove( )
. A
BlockingBuffer
can only be
demonstrated by an example that deals with multiple threads, and the
following code uses a Runnable
implementation, BufferListener
, which
is defined in Example
5-8:
import java.util.*; import org.apache.commons.collections.Buffer; import org.apache.commons.collections.buffers.BlockingBuffer; import org.apache.commons.collections.buffers.BoundedFifoBuffer; // Create a Blocking Buffer Buffer buffer = BlockingBuffer.decorate( new BoundedFifoBuffer( ) ); // Create Thread to continously remove( ) from the previous Buffer BufferListener listener = new BufferListener(buffer); Thread listenerThread = new Thread( listener ); listenerThread.start( ); buffer.add( "Hello World!" ); buffer.add( "Goodbye, Y'all." );
The previous example creates an instance of BufferListener
—a Runnable
object that calls remove( )
on a BoundedFifoBuffer
decorated with BlockingBuffer
. The listenerThread
will block on a call to
buffer.remove( )
within the run( )
method of BufferListener
, an object that runs in a separate thread and waits for
objects to be added to a BlockingBuffer
.
Example 5-8. A BufferListener constantly calling remove( )
public class BufferListener implements Runnable { private Buffer buffer; public BufferListener(Buffer buffer) { this.buffer = buffer; } public void run( ) { while(true) { String message = (String) buffer.remove( ); System.out.println( message ); } } }
The two calls to buffer.add( )
causes BufferListener
to print the
strings added:
Hello World! Goodbye, Y'all.
A BlockingBuffer
is used in a
system that needs to act on a piece a data as soon as it is available,
and this data structure comes in handy when there are a series of worker
threads listening to buffers between components in a pipeline. BlockingBuffer
allows you to build cascading
pipelines, which automatically notify the next stage of available data.
Think of this pattern as a stepped waterfall; water automatically flows
down the steps, and each step is a Buffer
. (See Figure 5-2.)
Assume that you need to write a workflow application for a news
publisher; the workflow consists of a pipeline: news stories are
published as XML files, which are passed to a search indexer and then
processed with an XSLT stylesheet. A news story is simply passed as a
String
containing an XML document.
The following example creates a pipeline consisting of two BlockingBuffer
instances terminated by an
UnboundedFifoBuffer
:
import java.util.*; import org.apache.commons.collections.Buffer; import org.apache.commons.collections.buffers.BlockingBuffer; import org.apache.commons.collections.buffers.UnboundedFifoBuffer; // Create a Blocking Buffer for each stage, last stage not a blocking buffer Buffer published = BlockingBuffer.decorate( new UnboundedFifoBuffer( ) ); Buffer indexed = BlockingBuffer.decorate( new UnboundedFifoBuffer( ) ); Buffer transformed = new UnboundedFifoBuffer( ); // Create a Thread that will watch the published Buffer and index a news story Indexer indexer = new Indexer(published, indexed); Thread indexerThread = new Thread( indexer ); indexerThread.start( ); // Create a Thread that will watch the indexed Buffer and style a news story Styler styler = new Styler(index, transformed); Thread stylerThread = new Thread( styler ); stylerThread.start( ); String newsStory = getNewsStory( ); published.add( newsStory );
The previous example creates three buffers to hold the results of
the stages of a pipeline—published
,
indexed
, and transformed
. Three Runnable
objects are created to perform the
task of processing each news story; the Indexer
object listens to the published
buffer and places its results in the
indexed
buffer, and the Styler
object listens to the indexed
buffer and places its results in the
transformed
buffer.
The Indexer
object implements
Runnable
and is constructed with two
Buffer
objects, inbound
and outbound
. The Indexer
, as shown in Example 5-9, continuously calls
remove( )
on a BlockingBuffer
and waits until a story is
available to process.
Example 5-9. An Indexer stage in a pipeline
public class Indexer implements Runnnable { private Buffer inbound; private Buffer outbound; public Indexer(Buffer inbound, Buffer outbound) { this.inbound = inbound; this.outbound = outbound; } public void run( ) { while(true) { String story = (String) inbound.remove( ); String processedStory = processStory( story ); outbound.add( processedStory ); } } public String processedStory(String story) { // Run story through a search indexer return story; } }
The Styler
is omitted because
it follows the exact same pattern. Every stage in this pipeline is a
Runnable
implementation running in a
thread and listening to an inbound buffer by calling (and blocking) on
inbound.remove( )
. Using this
mechanism allows your system to process information in parallel by
running separate stages in separate threads, and there is no need for a
controller to coordinate the actions of a complex system. A pipeline can
be extended by simply adding another stage with an additional BlockingBuffer
. This pattern is useful in a
system that models a very complex workflow; instead of attempting to
capture a complex symphony of coordination, break the system into
autonomous stages that only know about inputs and outputs.
A BlockingBuffer
in Commons
Collections is analogous to a BlockingQueue
in Java 5.0. A BlockingQueue
in Java 5.0 has an important
feature that is missing in Commons Collections 3.0 implementation of
BlockingBuffer
: in Java 5.0's
BlockingQueue
, you can specify a
timeout when adding and removing values from a
queue.