10.5 Separating Thread Logic from Execution Logic
Suppose you've just spent 6 or 7 hours, heads-down, coming up with 10
or 15 complicated threads,
many of which are interdependent, and
finally have the queuing, ordering, and timing all figured out. Just as
you're about to pop the top on a Coke™, your boss comes storming in,
asking for more functionality, and you realize you're going to need to add
another thread. While that in itself isn't hard, fitting them into the timing
of the other threads is, and suddenly, you're looking at a complete redesign.This is all too familiar for programmers working in multithreaded environments.
The problem is that in Java (at least, pre-Tiger Java), threads'
execution cycles are tied to the code they actually perform. Functionality
and timing are all tied together. If you've got Tiger, though, you can get
around this, and separate thread functionality from thread execution.
10.5.1 How do I do that?
One of the coolest new interfaces in the java.util.concurrent package
is Executor. This interface defines a means of supplying threads to an
object (which implements the Executor interface), and letting that object
deal with timing and running of the threads, rather than forcing you to
place this logic in your threading class. You add your Runnable objects to
the Executor (which generally adds them to an internal queue), and the
Executor then uses its own threads to peel off the objects and run them.While you can create your own implementations of this class, you're better
off using one of the pre-built implementations. These are not classes
unto themselves, but pre-configured Executor implementations,
each
returned from a factory method on the java.util.concurrent.Executors
class. Here's the basic rundown:
Single thread executor
Obtained with Executors.newSingleThreadExecutor( ), this results
in a pool size of 1, so tasks are executed one at a time.
Obtained with Executors.newFixedThreadPool(int poolSize), this
creates an executor with the specified number of threads to run the
tasks with which you supply it.
This executor, obtained with Executors.newCachedThreadPool( ),
will use as many threads as it needs to run the objects in its queue. It
will reuse threads as they become available, as well as create new
threads.
This executor is obtained with Executors.newScheduledThreadPool( ), or Executors.newSingleThreadScheduledExecutor( ), and is detailed
in Executing Tasks Without an ExecutorService.
So what happens when you have just one little Callable object that you
want to execute, and you don't need the overhead of ExectorService?
Well, it seems those Sun guys thought of everything (except perhaps
open-sourcing Java)you use FutureTask.
10.5.2 How do I do that?
java.util.concurrent.FutureTask can be wrapped around Callable objects, allowing them to behave like a Future implementation returned from ExecutorService.submit( ). The syntax is similar as well:
The methods available to FutureTask are similar to Future, so I'll leave it to you to check out the Javadoc. With the details from Using Callable Objects, you shouldn't have any problems.
FutureTask<BigInteger> task =
new FutureTask<BigInteger>(new RandomPrimeSearch(512));
new Thread(task).start( );
BigInteger result = task.get( );
10.5.3 What about...
...good old Runnable? Fortunately, plain old Thread and Runnable didn't get left out of the mix. You can wrap a FutureTask around a Runnable
object just as easily as you can around a Callable object, and the same
functionality applies. However, since Runnable's run( ) method doesn't return a result, the constructor is a bit different:
You have to supply a value to the constructor, of the type specified by
FutureTask<String> task =
new FutureTask<String>(new MyRunnableObject, "Success!");
your parameterization (in this example, a String), which is returned by
get( ) if execution is successful. While the above example is valid, there
are really only two common variants when using Runnable FutureTasks:
The first allows for discarding the result of get( ) altogether, and the second provides a true/false check for the result of get( ). You'd do well to use one of these yourself.You essentially just create an Executor, give it some tasks, and then
FutureTask<Object> task = new FutureTask<Object>(runnable, null);
FutureTask<Boolean> task = new FutureTask<Boolean>(runnable, true);
don't worry about it:
I realize this goes against the nature of control freaks (of which I am
Executor e = Executors.newFixedThreadPool(5);
e.execute(new RunnableTask1( ));
e.execute(new RunnableTask2( ));
e.execute(new RunnableTask3( ));
one), but the Executor is quite competent to handle the running of your
tasks.