6.2. The Executor Framework
Tasks are logical units of work, and threads are a mechanism by which tasks can run asynchronously. We've examined two policies for executing tasks using threadsexecute
In Chapter 5, we saw how to use bounded queues to prevent an overloaded application from running out of memory. Thread pools offer the same benefit for thread management, and java.util.concurrent provides a flexible thread pool implementation as part of the Executor framework. The primary abstraction for task execution in the Java class libraries is not Thread , but Executor , shown in Listing 6.3. Listing 6.3. Executor Interface.
Executor
may be a simple interface, but it forms the basis for a flexible and powerful framework for asynchronous task execution that supports a wide variety of task execution policies. It provides a standard means of decoupling
task submission
from
task execution
, describing tasks with
Runnable
. The
Executor
Executor
is based on the producer-consumer pattern, where activities that submit tasks are the
6.2.1. Example: Web Server Using Executor
Building a web server with an
Executor
is easy.
TaskExecutionWebServer
in Listing 6.4
In
TaskExecutionWebServer
, submission of the request-handling task is decoupled from its execution using an
Executor
, and its behavior can be changed merely by substituting a different
Executor
implementation. Changing
Executor
implementations or configuration is far less invasive than changing the way tasks are submitted;
Executor
configuration is
Listing 6.4. Web Server Using a Thread Pool.
We can easily modify TaskExecutionWebServer to behave like ThreadPer-TaskWebServer by substituting an Executor that creates a new thread for each request. Writing such an Executor is trivial, as shown in ThreadPerTaskExecutor in Listing 6.5. Listing 6.5. Executor that Starts a New Thread for Each Task.
Similarly, it is also easy to write an Executor that would make TaskExecutionWebServer behave like the single-threaded version, executing each task synchronously before returning from execute , as shown in WithinThreadExecutor in Listing 6.6. 6.2.2. Execution PoliciesThe value of decoupling submission from execution is that it lets you easily specify, and subsequently change without great difficulty, the execution policy for a given class of tasks. An execution policy specifies the "what, where, when, and how" of task execution, including: Listing 6.6. Executor that Executes Tasks Synchronously in the Calling Thread.
Execution policies are a resource management tool, and the optimal policy depends on the available computing resources and your
6.2.3. Thread Pools
A thread pool, as its
Executing tasks in pool threads has a number of advantages over the thread-per-task approach. Reusing an existing thread instead of creating a new one amortizes thread creation and
The class library provides a flexible thread pool implementation along with some useful predefined configurations. You can create a thread pool by calling one of the static factory
newFixedThreadPool. A fixed-size thread pool creates threads as tasks are submitted, up to the maximum pool size, and then attempts to keep the pool size constant (adding new threads if a thread dies due to an unexpected Exception ).
newCachedThreadPool.
A cached thread pool has more flexibility to reap idle threads when the current size of the pool exceeds the demand for processing, and to add new threads when demand
newSingleThreadExecutor.
A single-threaded executor creates a single worker thread to process tasks, replacing it if it dies unexpectedly. Tasks are
newScheduledThreadPool. A fixed-size thread pool that supports delayed and periodic task execution, similar to Timer . (See Section 6.2.5.) The newFixedThreadPool and newCachedThreadPool factories return instances of the general-purpose ThreadPoolExecutor , which can also be used directly to construct more specialized executors. We discuss thread pool configuration options in depth in Chapter 8. The web server in TaskExecutionWebServer uses an Executor with a bounded pool of worker threads. Submitting a task with execute adds the task to the work queue, and the worker threads repeatedly dequeue tasks from the work queue and execute them.
Switching from a thread-per-task policy to a pool-based policy has a big effect on application stability: the web server will no longer fail under heavy load.
[5]
It also degrades more gracefully, since it does not create thousands of threads that
6.2.4. Executor Lifecycle
We've seen how to create an
Executor
but not how to shut one down. An
Executor
implementation is likely to create threads for processing tasks. But the JVM can't exit until all the (nondaemon) threads have
Because an
Executor
processes tasks asynchronously, at any given time the state of previously submitted tasks is not immediately obvious. Some may have completed, some may be currently running, and others may be queued awaiting execution. In shutting down an application, there is a spectrum from
To address the issue of execution service lifecycle, the ExecutorService interface extends Executor , adding a number of methods for lifecycle management (as well as some convenience methods for task submission). The lifecycle management methods of ExecutorService are shown in Listing 6.7. Listing 6.7. Lifecycle Methods in ExecutorService .
The lifecycle
Tasks submitted to an ExecutorService after it has been shut down are handled by the rejected execution handler (see Section 8.3.3), which might silently discard the task or might cause execute to throw the unchecked RejectedExecutionException . Once all tasks have completed, the ExecutorService TRansitions to the terminated state. You can wait for an ExecutorService to reach the terminated state with awaitTermination , or poll for whether it has yet terminated with isTerminated . It is common to follow shutdown immediately by awaitTermination , creating the effect of synchronously shutting down the ExecutorService .( Executor shutdown and task cancellation are covered in more detail in Chapter 7.) LifecycleWebServer in Listing 6.8 extends our web server with lifecycle support. It can be shut down in two ways: programmatically by calling stop , and through a client request by sending the web server a specially formatted HTTP request. Listing 6.8. Web Server with Shutdown Support.
6.2.5. Delayed and Periodic TasksThe Timer facility manages the execution of deferred ("run this task in 100 ms") and periodic ("run this task every 10 ms") tasks. However, Timer has some drawbacks, and ScheduledThreadPoolExecutor should be thought of as its replacement. [6] You can construct a ScheduledThreadPoolExecutor through its constructor or through the newScheduledThreadPool factory.
A Timer creates only a single thread for executing timer tasks. If a timer task takes too long to run, the timing accuracy of other TimerTask s can suffer. If a recurring TimerTask is scheduled to run every 10 ms and another Timer-Task takes 40 ms to run, the recurring task either (depending on whether it was scheduled at fixed rate or fixed delay) gets called four times in rapid succession after the long-running task completes, or "misses" four invocations completely. Scheduled thread pools address this limitation by letting you provide multiple threads for executing deferred and periodic tasks.
Another problem with
Timer
is that it behaves poorly if a
TimerTask
throws an unchecked exception. The
Timer
thread doesn't catch the exception, so an unchecked exception thrown from a
TimerTask
terminates the timer thread.
Timer
also doesn't
OutOfTime
in Listing 6.9 illustrates how a
Timer
can become
If you need to build your own scheduling service, you may still be able to take advantage of the library by using a
DelayQueue
, a
BlockingQueue
implementation that provides the scheduling functionality of
ScheduledThreadPoolExecutor
. A
DelayQueue
manages a collection of
Delayed
objects. A
Delayed
has a delay time associated with it:
DelayQueue
lets you
take
an element only if its delay has
|