Pooling threads to execute short tasks

If you develop programs that execute many short-lived tasks, it’s wise to take advantage of a technique called thread pooling. Instead of creating a thread for each new task and discarding the thread when the task is done, you can create a pool of threads and give the pool each task to execute. If a thread in the pool is available, the task executes immediately. The thread returns to the pool when the task is done. Otherwise, the task waits for a thread to become available from the pool before executing.

J2SE 5.0 offers a new java.util.concurrent package, and in that package there are concurrency utilities that provide a pre-built thread pooling framework. The Executor interface in java.util.concurrent provides a single method, execute, that accepts a Runnable object as follows:

public interface Executor {
public void execute(Runnable command);

To use the thread pooling framework, you create an Executor instance, then you pass it some runnable tasks:

Executor executor = ...;

Then you create or find an implementation of the Executor interface. The implementation could run the task immediately, in a new thread, or serially. For example, here is an implementation that spawns a new thread for each task:

class MyExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();

The concurrency utilities also include a ThreadPoolExecutor class that offers support for many common pooling operations. With one of the four ThreadPoolExecutor constructors, you can specify options such as pool size, keep alive time, a thread factory, and a handler for rejected threads:

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) 


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) 


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) 


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

But you really don’t need to call a constructor. Instead, the Executors class of the java.util.concurrent package creates the thread pool for you. In the simplest case, you call the newFixedThreadPool method in the Executors class and pass in the number of threads you want in the pool. You then use ExecutorService, an interface that extends Executor, to either execute Runnable tasks or submit them. Calling the submit method of ExecutorService allows you to get a result back. The submit method also returns a Future object that you can use to check if the task is done.

Let’s run a test program to demonstrate the use of thread pools. First, here’s a program, NamePrinter, that notifies you when it starts, pauses for some amount of time, and then notifies you when it’s done.

public class NamePrinter implements Runnable {
private final String name;
private final int delay;
public NamePrinter(String name, int delay) {
this.name = name;
this.delay = delay;
public void run() {
System.out.println("Starting: " + name);
try {
} catch (InterruptedException ignored) {
System.out.println("Done with: " + name);

Here is the test program, UsePool. It creates a thread pool of size 3, and adds 10 tasks to it (that is, 10 runs of NamePrinter). The UsePool program then waits for the tasks to finish before calling shutdown and awaitTermination. An ExecutorService should be shutdown before being terminated. There is also a shutdownNow method which attempts an immediate shutdown. Termination here is even faster than through the shutdown method. The shutdownNow method returns a List of any remaining Runnable tasks.

import java.util.concurrent.*;
import java.util.Random;

public class UsePool {
public static void main(String args[]) {
Random random = new Random();
ExecutorService executor =
// Sum up wait times to know when to shutdown
int waitTime = 500;
for (int i=0; i<10; i++) {
String name = "NamePrinter " + i;
int time = random.nextInt(1000);
waitTime += time;
Runnable runner = new NamePrinter(name, time);
System.out.println("Adding: " + name + " / " + time);
try {
(waitTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {

Compile NamePrinter and UsePool, then run UsePool. Here’s a sample output run — note that each run will be unique with the random sleeps present:

   Adding: NamePrinter 0 / 30
   Adding: NamePrinter 1 / 727
   Adding: NamePrinter 2 / 980
   Starting: NamePrinter 0
   Starting: NamePrinter 1
   Starting: NamePrinter 2
   Adding: NamePrinter 3 / 409
   Adding: NamePrinter 4 / 49
   Adding: NamePrinter 5 / 802
   Adding: NamePrinter 6 / 211
   Adding: NamePrinter 7 / 459
   Adding: NamePrinter 8 / 994
   Adding: NamePrinter 9 / 459   
   Done with: NamePrinter 0
   Starting: NamePrinter 3
   Done with: NamePrinter 3
   Starting: NamePrinter 4
   Done with: NamePrinter 4
   Starting: NamePrinter 5
   Done with: NamePrinter 1
   Starting: NamePrinter 6
   Done with: NamePrinter 6
   Starting: NamePrinter 7
   Done with: NamePrinter 2
   Starting: NamePrinter 8
   Done with: NamePrinter 5
   Starting: NamePrinter 9
   Done with: NamePrinter 7
   Done with: NamePrinter 9
   Done with: NamePrinter 8

Notice that the first three NamePrinter objects started quickly. Later NamePrinter objects started as each executing NamePrinter object finished.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.