- divide the work into tasks => simplify program organization, facilitate error recovery, promote concurrency.
- Identify sensible task boundaries => improve flexibility in scheduling, facilitate concurrency.
- Server applications
- should exhibit both good throughput and good responsiveness
- should exhibit graceful degradation as they become overloaded
- use individual client requests as task boundaries => usually appropriate task sizing.
- Executing Tasks Sequentially
- The main thread alternates between accepting requests and processing the associated request.
- Processing a web request => a mix of computation and I/O => may block due to network congestion or connectivity problems.
- Explicitly Creating Threads for Tasks
- => Task processing is offloaded from the main thread, enabling the main loop to resume waiting for the next incoming connection more quickly => improve responsiveness.
- => Tasks can be processed in parallel, enabling multiple requests to be serviced simultaneously => improve throughput.
- => Task-handling code must be thread-safe, because it may be invoked conurrently for multiple tasks.
- Disadvantages of Unbounded Thread Creation
- Thread lifecycle overhead.
- Thread creation and teardown overhead => latency.
- Even worse if requests are frequent and lightweight.
- Resource consumption.
- Threads consume memory.
- If runnable threads more than available processors, threads sit idle.
- Stability
- There is a limit on how many threads can be created.
- Hit the limit =>
OutOfMemoryErro
=> risky to recover it.
- You need to place some bound on how many threads the application creates, and test thoroughly to ensure that.
- Thread lifecycle overhead.
java.util.concurrent
provides a flexible thread pool implementation as part of theExecutor
framework.- The primary abstraction for task execution is
Executor
. - => decoupling task submission from task execution, describing tasks with
Runnable
. - => let its implementations provide lifecycle support and hooks for statistics gathering, application management, and monitoring.
Executor
is based on the producer-consumer pattern.
- The primary abstraction for task execution is
- Example: Web Server Using Executor
- Execution Policies
- an execution policy := what, where, when, how of a task execution.
- policy depends on available computing resources and quality-of-service requirements.
- limiting the number of concurrent tasks => avoid failure due to resource exhaustion or contention for scarce resources.
- Thread Pools
- => manage a homogeneous pool of worker threads => tightly bounded by a work queue holding tasks waiting to be executed.
- worker thread: request the next task from the work queue, execute it, and go back to waiting for another task.
- executing tasks in thread pool => reusing existing threads amortizes thread creation and teardown costs, can have enough threads to keep the processors busy, while not running out of memory or thrashes due to competition.
- Create a thread pool through static factory methods in
Executors
: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.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 increases, but places no bounds on the size of the pool.newSingleThreadExecutor
: A single-threaded executor creates a single worker thread to process tasks, replacing it if it dies unexpectedly. Tasks are guaranteed to be processed sequentialy according to the order imposed by the task queue (FIFO, LIFO, priority order).newScheduledThreadPool
: A fixed-size thread pool that supports delayed and periodic task execution, similar toTimer
.
- Executor Lifecycle
- JVM can't exit until all the threads have terminated => failing to shut down an
Executor
could prevent the JVM from exiting. - shutdown := graceful shutdown + abrupt shutdown.
ExecutorService
extendsExecutor
to provide methods for lifecycle management (running, shutting down, terminated).- Tasks submitted to an
ExecutorService
after it has been shut down are handled by the rejected execution handler => might silently discard the task, or might causeexecute
to throw the uncheckedRejectedExecutionException
. - It is common to follow
shutdown
immediately byawaitTermination
.
- Tasks submitted to an
- JVM can't exit until all the threads have terminated => failing to shut down an
- Delayed and Periodic Tasks
ScheduledThreadPoolExecutor
should be thought of as replacement forTimer
.Timer
has some drawbacks.- => creates only a single thread for executing timer tasks => if a timer task takes too long => the timing accuracy can suffer.
- =>
TimerTask
throws unchecked exception, whileTimer
thread doesn't catch the exception =>Timer
would assume the entireTimer
was cancelled => scheduled but not yet executedTimerTask
s are never run.
DelayQueue
: aBlockingQueue
implementation that provides the scheduling functionality ofScheduledThreadPoolExecutor
.- => manages a collection of
Delayed
objects. - => let you
take
an element only if its delay has expired. - => objects are returned by the time associated with their delay.
- => manages a collection of
- Example: Sequential Page Renderer
- As text markup is encountered, render it into the image buffer; as image references are encountered, fetch the image over the network and draw it into the image buffer as well.
- The simplest approach is to process sequentially, but firstly leaving placeholders and going back to replace associated placeholders with appropriate content.
- Result-bearing Tasks:
Callable
andFuture
Runnable
can not return a value or throw checked exceptions.- deferred computations := executing a database query, fetching a resource over the network, or computing a complicated function.
Callable
=> it expects the main entry pointcall
, will return a value and anticipates that it might throw an exception => suitable for deferred computations.Future
: represents the lifecycle of a task and provides methods to test whether the task has completed or been cancelled, retrieve its result, and cancel the task.Future.get()
returns immediately or throws anException
if the task has already completed, but if not it blocks until the task completes.- If the task completes by throwing an exception,
get
rethrows it wrapped in anExecutionException
; if it was cancelled, get throwsCancellationException
. Ifget
throwsExecutionException
, the underlying exception can be retrieved withgetCause
.
- Create a
Future
:- The
submit
methods inExecutorService
all return aFuture
. - You can explicitly instantiate a
FutureTask
for a givenRunnable
orCallable
. ExecutorService
implementations can overridenewTaskFor
inAbstractExecutorService
to control instantiation of theFuture
.
- The
- Example: Page Renderer with Future
- divide the work into two tasks, one that renders the text (CPU-bounded) and one that downloads all the images (I/O-bounded).
- Limitations of Parallelizing Heterogeneous Tasks
- Heterogeneous tasks might have disparate sizes.
- The real performance payoff of dividing a program's workload into tasks comes when there are a large number of independent homogeneous tasks that can be processed concurrently.
- CompletionService: Executor Meets BlockingQueue
CompletionService
:=Executor
+BlockingQueue
- submit
Callable
tasks to it => use queue-like methodstake
andpoll
to retrieve completed results, packaged asFutures
, as they become availability.
- submit
ExecutorCompletionService
implementsCompletionService
, delgating the computation to anExecutor
.- The constructor creates a
BlockingQueue
to hold completed results. FutureTask
has adone
method that is called when the computation completes.- Submitted task is wrapped with a
QueueingFuture
(a subclass ofFutureTask
that overridesdone
to place the result on theBlockingQueue
). - The
take
andpoll
methods delegate to theBlockingQueue
, blocking if results are not yet available.
- The constructor creates a
- Multiple
ExecutorCompletionService
can share a singleExecutor
=> it is sensible to create anExecutorCompletionService
that is private to a particular computation while sharing a commonExecutor
.
- Example: Page Renderer with CompletionService
- create a separate task for downloading each image and execute them in a thread pool => turning the sequential download into a parallel one => reduces total runtime.
- fetch results from the
CompletionService
and rendering each image as soon as it is available => a dynamic and responsive user interface.
- Placing Time Limits on Tasks
- The timed version of
Future.get
returns as soon as the result is ready, but throwsTimeoutException
if the result is not ready within the timeout period. Future
can help; if a timedget
completes with aTimeoutException
, you can cancel the task through theFuture
.
- The timed version of
- Example: A Travel Reservations Portal
invokeAll
: submit multiple tasks to anExecutorService
and returns a collection ofFuture
s.