Suppose I already have an application-wide ThreadPoolExecutor
, and now I also need a ScheduledExecutorService
. Should I just change the implementation to ScheduledThreadPoolExecutor
and use that for both, scheduled and ordinary tasks? Or should I have a ThreadPoolExecutor
for ordinary tasks, and a separate ScheduledThreadPoolExecutor
for scheduled tasks? Is it a good idea to use ScheduledThreadPoolExecutor
for ordinary tasks, or is there something in it's implementation that makes it only useful for scheduled tasks?
My line of thinking is that a single ScheduledThreadPoolExecutor
would allow for the best thread usage, while it increases the risk of starvation if we have many scheduled tasks and few ordinary ones or vice versa.
For more context, I have an old codebase that still uses java.util.Timer
for scheduled tasks, and an application-wide ThreadPoolExecutor
for ordinary tasks. Since Timer
is single-threaded, our TimerTasks typically shift computation immediately to the thread pool once the Timer executes them, in order not to delay further TimerTasks. Something along these lines:
timer.schedule(new TimerTask() {
public void run() {
executor.execute(this::performWork);
}
}, 1000);
So far we postponed refactoring from Timer
to ScheduledThreadPoolExecutor
, but it's only now that I realize that we can eliminate constructs like the one above when using a single thread pool for all kinds of tasks.
I would recommend using two separate pools for two reasons. The first reason is that I find it easier from an application perspective to tune the pools if they are distinct. It is probably easier to predict the number of threads needed for scheduled tasks and for one-shot tasks than the combined workload. The second reason is from the API:
While this class inherits from ThreadPoolExecutor, a few of the inherited tuning methods are not useful for it. In particular, because it acts as a fixed-sized pool using corePoolSize threads and an unbounded queue, adjustments to maximumPoolSize have no useful effect. Additionally, it is almost never a good idea to set corePoolSize to zero or use allowCoreThreadTimeOut because this may leave the pool without threads to handle tasks once they become eligible to run.
In other words it doesn't quite behave as a regular ThreadPoolExecutor and tuning efforts can have surprising effects including starvation.