JDK-6452337 : ThreadPoolExecutor should prefer reusing idle threads
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 6
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2006-07-25
  • Updated: 2015-10-12
Related Reports
Relates :  
Description
Currently, ThreadPoolExecutor will aggressively create new threads when
poolSize < corePoolSize, even when an idle thread is available.
We should consider a way to enable the opposite behavior, even at the risk
of occasionally leaving a task temporarily unserviced in the queue.
This option would be used with allowCorePoolThreadTimeOut.

Excerpts from email follow:

I appreciate all your replies, and I definitely realize the design
tradeoffs that need to be made for the sake of efficiency. I believe I
tried to hack in the below "poll() before idling thread" + "keep approx
idle count" approach into the JDK code, though I was not very familiar
with the code and gave up quickly. 

Briefly, my use-case for having a thread pool behave as requested is
basically:

1) multiple thread pools (10 or so)
2) each with specific priority / thread group / naming / specific Thread
subclass factories
3) desire to let each pool grow very large (~1000 threads) due to
possibility of slow I/O in one or more of the pools & desire for maximum
concurrency in the tasks
4) pools hand off work between each other for roughly ~1000 concurrent
tasks

By not having #4 from previous message, the number of threads in the
system can peak up to 10K threads, even though there are only 1000
concurrent tasks. The conflicting needs are: 1) to have a large CORE
size for the pools to allow many concurrent threads when the I/O is
slow, but 2) to keep the average pool size low when the I/O is fast
(normal case), all the while 3) keeping the TOTAL thread count (globally
and per-pool) as low as reasonable. 

I won't say the architecture is ideal, but it is much easier to swap out
thread-pool implementations than it is to rewrite the app. Hope this
clarifies things a little.


Best Regards, 

Patrick


-----Original Message-----
>
>>>>IE I would like a:
>>>>
>>>>1) potentially infinite queue of tasks (currently using a 
>>>>LinkedBlockingQueue)
>>>>
>>>>2) executed by up to MAX threads concurrently
>>>>
>>>>3) which go away after a period of inactivity, allowing the pool to 
>>>>shrink to MIN (or zero) size
>>>>
>>>>4) preferring to use already existing threads rather than creating new

ones

>>>>
>
>> 
>> 
>> All but (4) are straightforward.
>> 
>> By (4) I assume you mean to use an existing thread only if
>> it is "idle". This is just about impossible to deterministically
>> guarantee, at least when using an unbounded queue.


>> The pool cannot know for sure whether an existing thread is idle.
>> It only knows whether there are fewer or more than target number
>> of threads, and whether the queue is full, which it never is for
>> unbounded ones. Problematic cases include those where an
>> idle-looking thread is in the process of dequeuing a task and
>> so will momentarily run it. You might be content with an
>> implementation that ignores such cases and approximates (4)
>> by for example, counting threads blocked on workQueue.take.
>> But it would probably be a bad idea for us to add such capabilities --
>> they invite a  stream of bug reports when "approximately" doesn't
>> cover particular use cases well. (For example, here, it is hard
>> to avoid the pathology of a large number of threads trying
>> to submit tasks at the same time, and all of them thinking that
>> they don't need to create a new thread because there is an idle one.)


I'm sympathetic to Patrick's suggestion; users generally
assume that ThreadPoolExecutor prefers reusing idle
threads.  I think we can avoid the above pathology by
simply checking queue size after enqueuing,
and changing our minds and adding a
new thread if the queue size is ever greater than 1.
Then at worst there will be one excess task waiting while all
workers are busy.

If we maintained good idleTaskCount statistics, we
could be more aggressive and only add a new thread if
idleTaskCount() < workQueue.size().

Maintaining an idle task count seems relatively expensive,
especially if the pool size is large.

Perhaps we could optimize it by
Try queue.poll(); if a task is available immediately, run it.
else increment idleCount while using timed poll() or take().

There are lots of design choices here.

Martin


>> ThreadPoolExecutor tries to cover as wide a set of use cases as
>> can all be handled by the same overall design. It does cover most
>> common uses. But even at that, we have had to revamp parts of the
>> implementation now and then to get rid of unwanted unforeseen
>> interactions among various parameters and methods.
>> 
>> So, if you really need this behavior, it looks like you will
>> need a custom implementation. Or you might decide that plain
>> newFixedThreadPools are OK for your application after all?
>> 
>> -Doug

Comments
EVALUATION It might be worth adding a new simpler ThreadPool implementation that: - has a maximumPoolSize (but no corePoolSize) - has a keepAliveTime - all threads are subject to timeout - has a BlockingQueue to hold waiting tasks - *always* enqueues new tasks - maintains an internal AtomicInteger count of idle worker threads - maintains the invariant that when queue.size() > 0, there is at least one idle worker, unless we've reached maximumPoolSize. When adding a task, - if no idle workers, start a new one and enqueue the task. else: - enqueue the task - recheck for queue non-empty and no idle workers; if so, start a new one. When a worker gets a new task and decrements idle count, - recheck for queue non-empty and no idle workers; if so, start a new one. A worker gets a new task like this: Runnable r = q.poll(); if (r != null) return r; else { increment idle count; try { return q.poll(timeout); } finally { decrement idle count; recheck for q non-empty and no idle workers; } } As always, details are trickier.
05-08-2006

EVALUATION I like this idea, but ThreadPoolExecutor is already complicated enough.
25-07-2006