ThreadPoolExecutor gotcha

I’ve recently discovered a “gotcha!” in the really useful standard Java class ThreadPoolExecutor. I think that this deserves a post, since it’s quite interesting and non obvious.

Executors

Executor and ExecutorService are really useful objects for designing multi-threaded applications in Java, and they have been introduced - alongside a lot of excellent other classes - in Java 5, as part of the java.util.concurrent package. If you don’t know about them, you should really read the excellent book Java concurrency in practice by Brian Goetz - it’s a great book about that package, and also for concurrent programming in general.

The most common way to get an instance of Executor is to use Executors.newFixedThreadPool or Executors.newCachedThreadPool, who return respectively an executor which always uses the given number of threads, or one executor which creates threads on demand (using a cache) and expires them after one minute of idling.

ThreadPoolExecutor

However, sometimes you might want to have more control: for instance you might want to limit the queue size, or to have the threads expire after a different idle timeout, or to use a thread pool that has some minimum and maximum size: for instance, you might want to use anywhere between 0 and 10 threads. In cases like this, you aren’t able to use newFixedThreadPool, since that would always allocate 10 threads; you can’t use newCachedThreadPool either, since there is no upper bound.

In cases like these, you can create an instance of ThreadPoolExecutor explicitly, using something like:

As I have recently discovered, this doesn’t do what you expect. Reading quickly the javadoc, you’d guess that this would create a new thread pool with anywhere between 0 and 10 threads, which expires them after 30 seconds of idle time, and with an unbounded LinkedBlockingQueue of tasks to execute. However, if you read the javadoc more carefully, you will find out that new threads are created only when the queue is full; since we are using an unbounded queue, this will never happen. Therefore, the thread pool will always use only one thread!

In this way, we create a thread pool which has 10 core threads, but we allow them to expire explicitly. Therefore, what happens is that the executor goes from 0 to 10 threads, as required, creating new threads when all the existing ones are busy and expiring them when idle.