8-Applying Thread Pools
8.1. Implicit Couplings Between Tasks and Execution Policies
*start*
ThreadLocal makes sense to use in pool threads only if the thread-local
value has a lifetime that is bounded by that of a task; Thread-Local should not be used in pool threads to communicate
values between tasks.
Mixing long-running and short-running tasks
risks “clogging” the pool unless it is very large; submitting tasks that depend on other tasks risks deadlock unless the
pool is unbounded.
Some tasks have characteristics that require or preclude a specific execution policy. Tasks that depend on other tasks
require that the thread pool be large enough that tasks are never queued or rejected; tasks that exploit thread
confinement require sequential execution. Document these requirements so that future maintainers do not undermine
safety or liveness by substituting an incompatible execution policy.
8.1.1. Thread Starvation Deadlock
Whenever you submit to an Executor tasks that are not independent, be aware of the possibility of thread starvation
deadlock, and document any pool sizing or configuration constraints in the code or configuration file where the
Executor is configured.
8.1.2. Long-running Tasks
One technique that can mitigate the ill effects of long-running tasks is for tasks to use timed resource waits instead of
unbounded waits.
If a
thread pool is frequently full of blocked tasks, this may also be a sign that the pool is too small.
8.2. Sizing Thread Pools
For compute-intensive tasks, an NcpuͲprocessor system usually achieves optimum utilization with a thread pool of Ncpu
+1 threads. (Even compute-intensive threads occasionally take a page fault or pause for some other reason, so an
“extra” runnable thread prevents CPU cycles from going unused when this happens.
int N_CPUS = Runtime.getRuntime().availableProcessors();8.3. Configuring ThreadPoolExecutor
*8.3.1. Thread Creation and Teardown* If the default execution policy does not meet your needs, you can instantiate a ThreadPoolExecutor through its
constructor and customize it as you see fit; you can consult the source code for Executors to see the execution policies
for the default configurations and use them as a starting point.
A thread that has been idle for longer than the keep-alive time becomes a
candidate for reaping and can be terminated if the current pool size exceeds the core size.
When a ThreadPoolExecutor is initially created, the core threads are not started immediately but instead as tasks are submitted, unless
you call prestartAllCoreThreads.
8.3.2. Managing Queued Tasks
There are three basic
approaches to task queuing: unbounded queue, bounded queue, and synchronous handoff.
SynchronousQueue is
a practical choice only if the pool is unbounded or if rejecting excess tasks is acceptable. The newCachedThreadPool
factory uses a SynchronousQueue.
The newCachedThreadPool factory is a good default choice for an Executor, providing better queuing performance
than a fixed thread pool.[5] A fixed size thread pool is a good choice when you need to limit the number of concurrent
tasks for resource-management purposes, as in a server application that accepts requests from network clients and
would otherwise be vulnerable to overload.
8.3.3. Saturation Policies
The saturation policy for a
ThreadPoolExecutor can be modified by calling setRejectedExecutionHandler.saturation policy: AbortPolicy,
CallerRunsPolicy, DiscardPolicy, and DiscardOldestPolicy.
8.3.4. Thread Factories
8.3.5. Customizing ThreadPoolExecutor After Construction8.4. Extending ThreadPoolExecutor
*8.4.1. Example: Adding Statistics to a Thread Pool*
8.5. Parallelizing Recursive Algorithms
*8.5.1. Example: A Puzzle Framework*
Summary
8.1. Implicit Couplings Between Tasks and Execution Policies
start
ThreadLocal makes sense to use in pool threads only if the thread-local
value has a lifetime that is bounded by that of a task; Thread-Local should not be used in pool threads to communicate
values between tasks.
Mixing long-running and short-running tasks
risks “clogging” the pool unless it is very large; submitting tasks that depend on other tasks risks deadlock unless the
pool is unbounded.
Some tasks have characteristics that require or preclude a specific execution policy. Tasks that depend on other tasks
require that the thread pool be large enough that tasks are never queued or rejected; tasks that exploit thread
confinement require sequential execution. Document these requirements so that future maintainers do not undermine
safety or liveness by substituting an incompatible execution policy.
8.1.1. Thread Starvation Deadlock
Whenever you submit to an Executor tasks that are not independent, be aware of the possibility of thread starvation
deadlock, and document any pool sizing or configuration constraints in the code or configuration file where the
Executor is configured.
8.1.2. Long-running Tasks
One technique that can mitigate the ill effects of long-running tasks is for tasks to use timed resource waits instead of
unbounded waits.
If a
thread pool is frequently full of blocked tasks, this may also be a sign that the pool is too small.
8.2. Sizing Thread Pools
For compute-intensive tasks, an NcpuͲprocessor system usually achieves optimum utilization with a thread pool of Ncpu
+1 threads. (Even compute-intensive threads occasionally take a page fault or pause for some other reason, so an
“extra” runnable thread prevents CPU cycles from going unused when this happens.
int N_CPUS = Runtime.getRuntime().availableProcessors();
8.3. Configuring ThreadPoolExecutor
8.3.1. Thread Creation and Teardown
If the default execution policy does not meet your needs, you can instantiate a ThreadPoolExecutor through its
constructor and customize it as you see fit; you can consult the source code for Executors to see the execution policies
for the default configurations and use them as a starting point.
A thread that has been idle for longer than the keep-alive time becomes a
candidate for reaping and can be terminated if the current pool size exceeds the core size.
When a ThreadPoolExecutor is initially created, the core threads are not started immediately but instead as tasks are submitted, unless
you call prestartAllCoreThreads.
8.3.2. Managing Queued Tasks
There are three basic
approaches to task queuing: unbounded queue, bounded queue, and synchronous handoff.
SynchronousQueue is
a practical choice only if the pool is unbounded or if rejecting excess tasks is acceptable. The newCachedThreadPool
factory uses a SynchronousQueue.
The newCachedThreadPool factory is a good default choice for an Executor, providing better queuing performance
than a fixed thread pool.[5] A fixed size thread pool is a good choice when you need to limit the number of concurrent
tasks for resource-management purposes, as in a server application that accepts requests from network clients and
would otherwise be vulnerable to overload.
8.3.3. Saturation Policies
The saturation policy for a
ThreadPoolExecutor can be modified by calling setRejectedExecutionHandler.saturation policy: AbortPolicy,
CallerRunsPolicy, DiscardPolicy, and DiscardOldestPolicy.
8.3.4. Thread Factories
8.3.5. Customizing ThreadPoolExecutor After Construction
8.4. Extending ThreadPoolExecutor
8.4.1. Example: Adding Statistics to a Thread Pool
8.5. Parallelizing Recursive Algorithms
8.5.1. Example: A Puzzle Framework
Summary