JDK-6450200 : ThreadPoolExecutor idling core threads don't terminate when core pool size reduced
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,linux
  • CPU: generic,x86
  • Submitted: 2006-07-19
  • Updated: 2011-05-18
  • Resolved: 2011-05-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b08Fixed
Related Reports
Duplicate :  
Relates :  
Description
ThreadPoolExecutor.setCorePoolSize goes to a lot of trouble to 
kill off excess idling threads, but the code is not correct.

--------------
import java.util.*;
import java.util.concurrent.*;

public class Bug4 {
    static void report(String label, ThreadPoolExecutor tpe) {
	System.out.printf("%10s: active=%d, submitted=%d, completed=%d%n",
			  label,
			  Thread.activeCount() - 1,
			  tpe.getTaskCount(),
			  tpe.getCompletedTaskCount());
    }

    public static void main(String[] args) throws Throwable {
	final int count = 8;
	final CyclicBarrier barrier = new CyclicBarrier(count + 1);
	ThreadPoolExecutor tpe =
	    new ThreadPoolExecutor(10, 30, 1L, TimeUnit.HOURS,
				   new LinkedBlockingQueue<Runnable>());

	report("newborn", tpe);

	for (int i = 0; i < count; i++)
	    tpe.execute(new Runnable() {
		    public void run() {
			try { barrier.await(); barrier.await(); }
			catch (Throwable t) { t.printStackTrace(); }
		    }});

	barrier.await();
	report("started", tpe);

	barrier.await();
	Thread.sleep(1000);
	report("idling", tpe);

	tpe.setCorePoolSize(count/2);
	Thread.sleep(1000);

	report("shrunk", tpe);

	System.out.printf("Shutting down...%n");
	tpe.shutdown();
	tpe.awaitTermination(1L, TimeUnit.HOURS);

	report("terminated", tpe);
    }
}
--------------
 $ jver 6 jr Bug4
==> javac -source 1.6 -Xlint:all Bug4.java
==> java -esa -ea Bug4
   newborn: active=0, submitted=0, completed=0
   started: active=8, submitted=8, completed=0
    idling: active=8, submitted=8, completed=8
    shrunk: active=8, submitted=8, completed=8
Shutting down...
terminated: active=0, submitted=8, completed=8

Comments
PUBLIC COMMENTS I disagree that the submitter is correct. The spec states that if the pool size is reduced then core threads will terminate when they next become *idle*. However it does not clearly specify what idle means. The code interprets idle as "not finding any work to process within the keepalive time". The test program sets a keepalive time of one hour and so the threads are not considered idle during the execution of the test. I think the spec docs can be clarified as to what idle means, but otherwise this is working-as-designed and I consider this "not a bug". *** (#1 of 3): 2006-07-19 11:23:47 EST ###@###.### I just wrote a lengthy discussion of why reducing the coreSize does not guarantee any change in the current poolSize when I realized that the interruptIfIdle code doesn't even cause any thread to immediately terminate. If a thread is idle - which means it is either on its way to get a task from the queue, blocked waiting for a task in the queue, or preparing to execute a task it just got from the queue - then in getTask the interrupt will only cause the loop to be retried. At that point if the reduction in coreSize means that the poolSize is now larger than core, then the "excess" thread will do a timed poll rather than a take(). Hence only if the thread is blocked for the keepAlive time *after being interrupted* will it even attempt to terminate. But for the thread to actually terminate it has to get past workerCanExit() and this will (under normal pool operating conditions) only return true if the workQueue is empty, or core threads can timeout and poolSize > coreSize. In short it is extremely unlikely that a thread will terminate until the pool is quiescent (workqueue empty) and the keepAlive time has expired. To that end the interruptIfIdle call simply potentially nudges the thread out of a block-forever take() into a block-till-timed-out poll(). Further the interrupt may fail for security reasons and as the comment in the code says "Not an error: it is OK if the threads stay alive". So reducing the number of threads is a heuristic, not something that must be guaranteed - indeed it can not be guaranteed. I would argue that you would not want threads to "agressively" terminate just because the coreSize has been reduced. Suppose the pool is operating at M threads where coreSize < M < Max, and the queue is full. If the coreSize is reduced what would be a reasonable response by this pool given that it is working flat out? I think it would be incorrect to force the termination of threads just because the coreSize was reduced, because those threads would have been created to service the current workload (we're still under maximum size) and by terminating them we reduce the throughput capability of the pool. (It is like having a factory line with a set of permanent workers and a bunch of casuals to help out when demand is high. You might decide to reduce the number of permanent workers but you don't tell them to go in the middle of a shift when everyone is flat out.) The corePoolSize has two effects on the pool: 1. Whether a thread is created upon submission 2. The *minimum* number of threads in the pool (modulo allowing core threads to timeout, and the fact that minimum has to be reached through submission of at least that many concurrent tasks) Changing the coreSize dynamically will have an immediate affect on (1). It doesn't have to have any immediate effect on (2) because the "extra" threads do not affect the minimum number of threads in the pool. The only time the minimum number of threads and the current number of threads should be guaranteed the same, is when the pool is quiet (workQueue is empty) and the setting of a keepAlive time has allowed the pool to shrink. And that is the only guarantee the current code gives. If anything a bug in the current code is that dropping the maximum pool size does not cause excess threads to terminate more quickly. Arguably you drop the maximum because there is a resource shortage and you need to scale down, so you want that to happen as soon as practical. Finally I also note that when reducing the coreSize interruptIfIdle is only called if the workQueue is full. I would have expected this test to be for the workqueue not being full? If it is not full then you should only expect up to coreSize threads to be active, hence an extra one could terminate. If it is full then up to max threads could be active so you shouldn't terminate the extra one. It might be best to take this discussion to email and then summarise in the comments and/or evaluation. *** (#3 of 3): 2006-07-19 14:19:28 EST ###@###.###
16-05-2011

EVALUATION Submitter is correct; "active" should go down to 4.
19-07-2006