Java 1.5 Tiger A Developers Notebook [Electronic resources]

David Flanagan, Brett McLaughlin

نسخه متنی -صفحه : 131/ 98
نمايش فراداده

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.

Fixed thread executor

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.

Cached thread executor

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.

Scheduled thread executor

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:

FutureTask<BigInteger> task =
new FutureTask<BigInteger>(new RandomPrimeSearch(512));
new Thread(task).start( );
BigInteger result = task.get( );

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.

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:

FutureTask<String> task =
new FutureTask<String>(new MyRunnableObject, "Success!");

You have to supply a value to the constructor, of the type specified by 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:

FutureTask<Object> task = new FutureTask<Object>(runnable, null);
FutureTask<Boolean> task = new FutureTask<Boolean>(runnable, true);

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 don't worry about it:

Executor e = Executors.newFixedThreadPool(5);
e.execute(new RunnableTask1( ));
e.execute(new RunnableTask2( ));
e.execute(new RunnableTask3( ));

I realize this goes against the nature of control freaks (of which I am one), but the Executor is quite competent to handle the running of your tasks.