ThreadPoolExecutor with corePoolSize 0 should not execute tasks until task queue is full
While running this program in jdk 1.5,1.6,1.7 and 1.8, I found different implementations of ThreadPoolExecutor#execute(Runnable)
in 1.5,1.6 and 1.7+. Here's what I found:
JDK 1.5 implementation
//Here poolSize is the number of core threads running.
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
for (;;) {
if (runState != RUNNING) {
reject(command);
return;
}
if (poolSize < corePoolSize && addIfUnderCorePoolSize(command))
return;
if (workQueue.offer(command))
return;
Runnable r = addIfUnderMaximumPoolSize(command);
if (r == command)
return;
if (r == null) {
reject(command);
return;
}
// else retry
}
}
This implementation does not create a thread when corePoolSize
is 0, therefore the supplied task does not execute.
JDK 1.6 implementation
//Here poolSize is the number of core threads running.
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
JDK 1.6 creates a new thread even if the corePoolSize
is 0.
JDK 1.7+ implementation(Similar to JDK 1.6 but with better locks and state checks)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
JDK 1.7 too creates a new thread even if the corePoolSize
is 0.
So, it seems that corePoolSize=0
is a special case in each versions of JDK 1.5 and JDK 1.6+.
But it is strange that the book's explanation doesn't match any of the program results.
This odd behavior of ThreadPoolExecutor
in Java 5 when the core pool size is zero was apparently recognized as a bug and quietly fixed in Java 6.
Indeed, the problem reappeared in Java 7 as a result of some code reworking between 6 and 7. It was then reported as a bug, acknowledged as a bug and fixed.
Either way, you should not be using a version of Java that is affected by this bug. Java 5 was end-of-life in 2015, and the latest available versions of Java 6 and later are not affected. That section of "Java Concurrency In Practice" is no longer apropos.
References:
- http://cs.oswego.edu/pipermail/concurrency-interest/2006-December/003453.html (read the entire thread)
- http://gee.cs.oswego.edu/dl/concurrency-interest/index.html (see the version of
ThreadPoolExecutor
in the JSR166y bundle.) - https://bugs.openjdk.java.net/browse/JDK-7091003)