Free tools Windows power users keep installed
One-click scans. No signup required.
Create one ExecutorService for the component that owns your background work, then submit each task to that same instance. For a simple concurrency limit, use Executors.newFixedThreadPool(n); use ThreadPoolExecutor when you also need to control queue capacity and overload handling. Shut the executor down when its owner is finished accepting work.
How to create a thread pool in Java
A Java thread pool is commonly represented by an ExecutorService. This example creates a fixed pool with four workers and gives the component that owns the work responsibility for closing it:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public final class WorkerService implements AutoCloseable {
private final ExecutorService pool = Executors.newFixedThreadPool(4);
public void submitWork(Runnable task) {
pool.submit(task);
}
@Override
public void close() {
pool.shutdown();
}
}
Call submit or execute on the executor to schedule work. Use submit when you want a Future for a task’s result or completion; execute accepts a Runnable without returning one.
How to reuse the same thread pool
Keep the executor as a field or otherwise retain it for the lifetime of the service or application component that produces the work. Submit each new Runnable or Callable to that same instance rather than constructing an executor inside the per-task method. With a fixed pool, each worker can take another queued task after finishing its current task, so repeated submissions reuse workers.
Reuse means reusing the executor instance and its workers across submissions. It does not mean restarting an executor after shutdown: once shutdown has begun, do not submit additional work. If the component later needs a pool again, create a new executor under a new lifecycle.
Which Java thread pool should you choose?
| Option | Behavior | Use when | Trade-off |
|---|---|---|---|
Executors.newFixedThreadPool(n) |
Uses a fixed number of workers and a shared unbounded queue. At most n tasks execute at once; further tasks wait in the queue. |
You need a straightforward limit on concurrent workers. | The queue is unbounded, so a backlog can grow. Add admission control or use a custom executor if queued work also needs a limit. Oracle API documentation. |
Executors.newCachedThreadPool() |
Creates workers as needed, reuses available workers, and retires idle workers after 60 seconds. | Tasks arrive in bursts and are short-lived. | Sustained demand can create many threads. Choose explicit bounds when resource usage needs tighter control. Oracle API documentation. |
ThreadPoolExecutor |
Lets you specify core and maximum pool sizes, keep-alive time, queue, thread factory, and rejection policy. | You need to set capacity and define what happens under overload. | More configuration means you must choose settings deliberately and manage the executor’s lifecycle. Oracle API documentation. |
The linked API pages document Java SE 21. Factory methods and defaults can differ by JDK version, so check the API documentation for the JDK your application targets.
Rank #2
Fixed pool: a worker limit, not a backlog limit
A fixed pool replaces a worker that terminates unexpectedly before shutdown, and its workers otherwise remain available until the executor is shut down. Its unbounded queue is the important capacity caveat: limiting active threads does not limit how many tasks can wait.
Cached pool: flexibility with less predictable thread growth
A cached pool can expand when tasks arrive and reuse workers that are available. Its 60-second idle-worker retirement can suit bursty, short-lived work, but it does not impose a maximum worker count.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
ThreadPoolExecutor: explicit capacity and rejection choices
Choose ThreadPoolExecutor when a factory’s defaults do not match your capacity needs. Configure its queue and rejection policy along with pool sizes: these determine what happens when workers and queue capacity cannot accept more work.
When and how to shut down ExecutorService
The component that creates or owns the executor should stop accepting new work and initiate shutdown when that work is finished. A graceful shutdown lets previously submitted tasks run; call awaitTermination when the caller must know whether they have completed within a bounded time.
Rank #4
pool.shutdown();
try {
if (!pool.awaitTermination(30, java.util.concurrent.TimeUnit.SECONDS)) {
// Apply an application-specific escalation policy here.
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
The timeout is an example, not a universal setting; choose one that fits the application. Preserve the caller’s interrupt status when catching InterruptedException, as shown.
Escalate only when graceful shutdown is insufficient
shutdownNow() is an escalation path: it attempts to interrupt running tasks and returns tasks that never commenced. Interruption is cooperative, so task code should respond to interruption for cancellation or shutdown to complete cleanly. A forced stop can leave work partially completed; callers should account for that possibility.
Quick Recap
Best Value
Common mistakes to avoid
- Creating a pool for every task: retain and reuse the executor owned by the component producing the work.
- Assuming a fixed pool bounds all queued work:
newFixedThreadPoollimits workers, but its shared queue is unbounded. - Submitting after shutdown: shutdown is a lifecycle transition, not a pause; create a new executor if the component later needs one.
- Shutting down without considering task completion: use
awaitTerminationwith a suitable bound when completion must be observed. - Ignoring interruption: make long-running tasks respond to interruption so cancellation and shutdown can progress.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

