Why Executors Should Be Avoided for ThreadPool Creation in Java

This article explains the definition of thread pools, the pitfalls of using Executors factory methods, details the ThreadPoolExecutor constructor parameters, compares different Executors implementations, demonstrates OOM tests, and offers guidelines for configuring thread pool parameters and rejection policies.

Senior Brother's Insights
Senior Brother's Insights
Senior Brother's Insights
Why Executors Should Be Avoided for ThreadPool Creation in Java

Thread pools manage a group of worker threads, offering benefits such as reduced memory overhead, lower system latency, and improved stability by preventing uncontrolled thread creation.

Thread Pool Definition

A thread pool reuses threads to execute tasks, reducing the cost of creating new threads and avoiding OutOfMemoryError (OOM) caused by unlimited thread creation.

Executors Creation Methods

Executors can create three types of thread pools that return different objects:

ThreadPoolExecutor

ScheduledThreadPoolExecutor

ForkJoinPool

The article focuses on the methods that return ThreadPoolExecutor objects.

ThreadPoolExecutor Constructor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

Parameter meanings: corePoolSize: number of core threads maximumPoolSize: maximum number of threads keepAliveTime: idle thread lifetime unit: time unit for

keepAliveTime
workQueue

: task queue used by the pool threadFactory: factory for creating new threads handler: policy for rejected tasks

Task Execution Logic

The pool processes tasks in the following order:

If the number of core threads is not full, a new core thread is created to run the task.

If core threads are full, the task is offered to workQueue. If the queue is not full, the task is queued.

If the queue is full, the pool checks whether it can create additional threads up to maximumPoolSize. If possible, a non‑core thread is created.

If the pool is also full, the handler rejection policy is applied.

Executors Methods Returning ThreadPoolExecutor

Executors.newCachedThreadPool()

– creates a pool with corePoolSize=0, maximumPoolSize=Integer.MAX_VALUE, keepAliveTime=60s, and a SynchronousQueue. Threads are created on demand and idle threads are reclaimed after 60 seconds, but unlimited thread creation can cause OOM. Executors.newSingleThreadExecutor() – creates a pool with a single core thread ( corePoolSize=1) and an unbounded LinkedBlockingQueue. The queue can grow indefinitely, leading to OOM, while maximumPoolSize and keepAliveTime become ineffective. Executors.newFixedThreadPool(int n) – creates a pool with fixed size n (both core and max equal to n) and an unbounded LinkedBlockingQueue. Similar OOM risk as the single‑thread pool.

OOM Test

A test class TaskTest.java repeatedly submits tasks to a cached thread pool with JVM heap limited to 10 MB ( -Xms10M -Xmx10M). The program quickly creates tens of thousands of threads and throws an OutOfMemoryError when the thread count exceeds the heap capacity.

How to Define Thread Pool Parameters

CPU‑bound workloads : pool size ≈ CPU count + 1 (obtainable via Runtime.availableProcessors()).

I/O‑bound workloads : CPU count × CPU utilization × (1 + waitTime / cpuTime).

Mixed workloads : separate pools for CPU‑bound and I/O‑bound tasks.

Blocking queue : prefer bounded queues to prevent resource exhaustion.

Rejection policy : default AbortPolicy throws RejectedExecutionException. Alternatives include:

Catch the exception and handle the task manually. CallerRunsPolicy – runs the task in the submitting thread, throttling submissions.

Custom implementation of RejectedExecutionHandler. DiscardPolicy or DiscardOldestPolicy – silently drop tasks when overloaded.

Using a Semaphore to limit concurrent submissions can also mitigate OOM when creating pools via Executors static methods.

Conclusion

Because Executors factory methods often configure pools with unbounded queues or unlimited thread counts, they can easily lead to OutOfMemoryError. Directly constructing ThreadPoolExecutor with carefully chosen parameters is recommended.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyOOMThreadPoolExecutorExecutorsThread Pool Configuration
Senior Brother's Insights
Written by

Senior Brother's Insights

A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.