As mentioned in Chapter
8, J2SE5.0 adds numerous enhancements to the threading control
and concurrency features of Java. Some of the enhancements are advanced
features beyond the scope of this book, but we explain some of the
simpler new features here.
The Executor Class
The most important new feature for the casual developer
of multithreaded applications is the new Executor
framework. A java.util.concurrent.Executor
is an object that executes Runnable
tasks. It is similar to calling
new Thread
(aRunnableObject).start ();
For a single new thread, there is perhaps not much
reason to use an Executor.
However, most multithreaded applications involve several threads.
Threads need stack and heap space, and, depending on the platform,
thread creation can be expensive. In addition, cancellation and
shutdown of threads can be difficult, as seen in Chapter
8, and a well-designed and implemented thread
pooling scheme is not simple.
The new Executor
framework solves all those problems in a way that decouples
task submission from the mechanics of how each task will be run,
including the details of thread use, scheduling, etc.
An Executor
can and should be used instead of explicitly creating threads. For
example, rather than creating a new thread and starting it as above,
you can use:
Executor executor
= some Executor factory method;
exector.execute (aRunnable);
Notice that our Executor
object was returned by an Executor
factory. (We discuss design patterns like factory methods in Chapter
16; a factory is a standard name for a method that is
used to obtain an instance of a class or subclass rather than making
it with a constructor.)
There are several static Executor
factory methods available in the java.util.concurrent.Executors
class. If you have several Runnable
tasks to carry out, but do not have specific requirements about
the order in which they occur, then a simple thread pool arrangement
is provided as follows:
Executor executor
= Executors.newFixedThreadPool (5);
executor.execute (new RunnableTask1 ());
executor.execute (new RunnableTask2 ());
executor.execute (new RunnableTask3 ());
...
Here the tasks run and complete under the control
of the Executor,
which reuses threads from the thead pool as needed without incurring
the overhead of always creating new threads.
The newFixedThreadPool
() method actually returns an object of type ExecuterService,
which is a subinterface of Executer. The
ExecuterService includes a number of methods
for managing a thread pool. For example, the above pool could be
stopped with
((ExecuterService)executor).shutdown();
As explained in the specification,
this means that no further tasks can be submitted and currently
running threads will continue until they finish. The shutdownNow()
method will make "best-effort attempts to stop processing
actively executing tasks", but there is no guarantee that a
thread will be stopped (e.g. if it is in an endless loop in the
run() method.) See Chapter
8: Java: Stopping Threads for advice on properly killing a thread.
There are several more Executor
factories in the Executors
class for other needs beyond the scope of this book. Refer to the
J2SE 5.0 API docs for complete information.
The Callable
Interface
The new java.util.concurrent.Callable
interface is much like Runnable
but overcomes two drawbacks with Runnable.
The run()
method in Runnable
cannot return a result (i.e. it returns void)
and cannot throw a checked exception. If you try to throw an exception
in a run()
method, the javac
compiler insists that you use a throws clause in the method signature.
However, the superclass run()
method doesn't throw an exception, so javac
will not accept this.
If you need a result from a Runnable
task, you have to provide some external means of getting that result.
A common technique is to set an instance variable in the Runnable
object and provide a method to retrieve that value. For example,
public MyRunnable
implements Runnable
{
private int fResult = 0;
public void run () {
...
fResult = 1;
} // run
// A getter method to provide the result of the thread.
public int getResult () { return fResult; }
} // class MyRunnable
The Callable
interface solves these problems. Instead of a run()
method the Callable
interface defines a single call()
method that takes no parameters but is allowed to throw an exception.
A simple example is
import java.util.concurrent.*;
public class MyCallable implements Callable
{
public Integer call () throws java.io.IOException
{
return 1;
}
} // MyCallable
This call()
method returns an Integer.
(Note that we have conveniently used the autoboxing
support in J2SE 5.0 to have the literal int
1 value automatically boxed into an Integer
return value.)
Getting the return value from a Callable
depends upon the new generics feature:
FutureTask
task = new FutureTask (new MyCallable ());
ExecutorService es = Executors.newSingleThreadExecutor ();
es.submit (task);
try {
int result = task.get ();
System.out.println ("Result from task.get () = " +
result);
}
catch (Exception e) {
System.err.println (e);
}
es.shutdown ();
Here, we use the FutureTask
class that supports an Integer
return value. Then the task is submitted using the ExecutorService
submit() method, and the result is obtained from the FutureTask
get() method, again using auto-unboxing to convert the Integer
to an int. See the API documentation for more information on ExecutorService,
and FutureTask.
Other Concurrency
Enhancements
We discuss some other new concurrency tools in Chapter
8: Supplements: More About Concurrency. Other enhancements in
the java.util.concurrency package not discussed here include advanced
atomic types, new high-performance thread-safe collections ConcurrentHashMap,
CopyOnWriteArrayList,
and CopyOnWriteArraySet.
See the API documentation for more information on these new collections
and other features.
References & Web Resources
Latest update: Apr.30, 2006
|