ThreadPoolExecutor Rejection Policies: Risks of CallerRunsPolicy and Mitigation Strategies
This article explains the various ThreadPoolExecutor rejection policies, highlights the risks of using CallerRunsPolicy, provides code examples, and suggests mitigation strategies such as adjusting queue size, thread count, or persisting overflow tasks to databases or message queues.
This article starts with an interview question about ThreadPoolExecutor rejection policies and expands to discuss the risks associated with the CallerRunsPolicy and possible solutions.
When the number of active threads reaches the maximum and the task queue is full, ThreadPoolExecutor can use one of several policies:
ThreadPoolExecutor.AbortPolicy : throws a RejectedExecutionException to reject the new task.
ThreadPoolExecutor.CallerRunsPolicy : runs the rejected task in the thread that invoked execute ; if the executor is shut down, the task is discarded.
ThreadPoolExecutor.DiscardPolicy : silently discards the new task.
ThreadPoolExecutor.DiscardOldestPolicy : discards the oldest unprocessed task in the queue.
In Spring, if no RejectedExecutionHandler is specified, the default is AbortPolicy , which throws a RejectedExecutionException when the queue is full.
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() {}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
// Directly run the task in the calling thread
r.run();
}
}
}If tasks must not be dropped, CallerRunsPolicy is the appropriate choice because it never discards tasks or throws exceptions.
However, when the rejected task is time‑consuming and the calling thread is the main thread, this policy can block the main thread, leading to thread‑pool blockage and even OOM. An example using Hutool's ThreadUtil demonstrates how the fourth task is executed by the main thread, causing subsequent tasks to wait.
Logger log = LoggerFactory.getLogger(ThreadPoolTest.class);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), new ThreadPoolExecutor.CallerRunsPolicy());
// submit tasks ...To mitigate this risk, you can increase the blocking queue size, raise maximumPoolSize , or adjust heap memory so that more tasks can be queued without exhausting resources.
Another approach is task persistence: store overflow tasks in a MySQL table, cache them in Redis, or push them to a message queue. A custom RejectedExecutionHandler can insert tasks into the database when the queue is full, and a hybrid blocking queue can prioritize fetching tasks from the database before the in‑memory queue.
Alternative rejection strategies from other frameworks include Netty's NewThreadRunsPolicy , which creates a temporary thread for the rejected task, and ActiveMQ's timed‑offer policy that attempts to enqueue the task within a configurable timeout.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.