Stop Creating Threads Manually: Master ThreadPoolExecutor, ThreadFactory, and BlockingQueue in Java
Modern backend systems process thousands of tasks simultaneously — API requests, background jobs, data processing, file uploads, and more…
Stop Creating Threads Manually: Master ThreadPoolExecutor, ThreadFactory, and BlockingQueue in Java
Modern backend systems process thousands of tasks simultaneously — API requests, background jobs, data processing, file uploads, and more. Creating a new thread for every task might seem simple, but it quickly becomes inefficient and dangerous.
Java solves this problem using the Executor Framework, especially ThreadPoolExecutor, combined with ThreadFactory and BlockingQueue.

In this article, we will walk through:
- The problem with naive thread creation
- How thread pools solve it
- The role of ThreadPoolExecutor
- Why BlockingQueue is critical
- How ThreadFactory helps customize threads
- Real-world production scenarios
The Problem: Creating Threads Manually
Many developers initially write code like this:
for(int i=0;i<1000;i++){
new Thread(() -> {
processTask();
}).start();
}
At first glance this looks fine.
But imagine this in production where:
- 10,000 requests arrive simultaneously
- Each request creates a thread
- Each thread consumes memory and CPU
Problems
- High Memory Usage: Each thread requires stack memory.
- Context Switching Overhead: Too many threads slow down the CPU scheduler.
- Uncontrolled Resource Usage: The system can crash due to thread explosion.
- No Task Queueing: If threads are busy, new tasks cannot wait.
This is where Thread Pools come in.
The Solution: Thread Pools
Instead of creating new threads for every task:
- Create a fixed number of reusable threads
- Put tasks into a queue
- Worker threads pick tasks from the queue
This is exactly what ThreadPoolExecutor does.
Java provides it via:
java.util.concurrent.ThreadPoolExecutor
What is ThreadPoolExecutor?
ThreadPoolExecutor is the core implementation of Java’s thread pool system.
It manages:
- Worker threads
- Task queue
- Thread lifecycle
- Task execution
Basic Constructor:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
workQueue,
threadFactory,
handler
);
Important Parameters

How ThreadPoolExecutor Works Internally
Understanding the execution flow helps design better systems.
1. Task Submission
executor.submit(task);
2. Check Core Threads
If active threads < corePoolSize
➡ create a new thread.
3. Add to Queue
If core threads are busy
➡ task goes into BlockingQueue
4. Create Extra Threads
If queue is full and threads < maximumPoolSize
➡ create new thread.
5. Reject Task
If queue is full and max threads reached
➡ RejectedExecutionHandler is triggered.
What is BlockingQueue?
A BlockingQueue is a thread-safe queue where:
- Producers add tasks
- Worker threads take tasks
If queue is empty: ➡ worker thread waits
If queue is full: ➡ producer waits
This prevents:
- CPU wastage
- busy waiting
- race conditions
Common BlockingQueue Implementations

Example:
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(100);
This means only 100 tasks can wait.
What is ThreadFactory?
When using ThreadPoolExecutor, new worker threads need to be created whenever the pool expands. Instead of creating threads directly using new Thread(), Java delegates this responsibility to a ThreadFactory.
ThreadFactory is an interface located in the java.util.concurrent package.
public interface ThreadFactory {
Thread newThread(Runnable r);
}
This interface contains only one method:
Thread newThread(Runnable r)
Its responsibility is simple:
Create and return a new
Threadthat will execute the givenRunnabletask.
So whenever a thread pool needs a new worker thread, it calls this method.
Why Does ThreadFactory Exist?
Without ThreadFactory, the executor would create threads like this internally:
new Thread(task)
This is fine for simple programs, but in production systems we often need more control over threads.
Examples of customization:
- Set thread names
- Set thread priority
- Configure daemon threads
- Add logging or monitoring
- Assign thread groups
ThreadFactory allows us to control how threads are created.
Default Implementation of ThreadFactory Java already provides a default implementation internally called:
Executors.DefaultThreadFactory
When you create a thread pool like this:
ExecutorService executor = Executors.newFixedThreadPool(5);
Java internally uses DefaultThreadFactory. Threads created by it look like:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
This naming convention helps debugging.
Internal Flow of Thread Creation
When ThreadPoolExecutor needs a new thread, the following happens:
Task Submitted
│
▼
ThreadPoolExecutor checks thread count
│
▼
Need new worker thread?
│
▼
Call ThreadFactory.newThread(Runnable)
│
▼
ThreadFactory creates Thread
│
▼
Thread starts executing task
So the ThreadFactory acts as a thread creation strategy.
Simple Custom ThreadFactory Example: Now let’s create our own custom thread factory.
import java.util.concurrent.ThreadFactory;
public class CustomThreadFactory implements ThreadFactory {
private int counter = 0;
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("worker-thread-" + counter++);
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
}
What this does:
- Assigns custom thread names
- Sets thread priority
Real-World Production Scenario
Let’s say you are building a payment processing system.
Requests arrive:
- Validate payment
- Fraud detection
- Database update
- Notification sending
Instead of creating threads for each request:
You create a ThreadPoolExecutor.
Architecture
Incoming Tasks
│
▼
BlockingQueue
│
▼
Worker Threads (ThreadPoolExecutor)
│
▼
Process Tasks
Benefits:
- Controlled concurrency
- Efficient CPU usage
- Predictable system behavior
Example Production Ready Code
import java.util.concurrent.*;
public class ExecutorExample {
public static void main(String[] args) {
ThreadPoolExecutor executor =
new ThreadPoolExecutor(
2,
4,
30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new CustomThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 20; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println(Thread.currentThread().getName()
+ " processing task " + taskId);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
executor.shutdown();
}
}
When Should You Use ThreadPoolExecutor?
Use it when:
✔ Processing API requests ✔ Handling background jobs ✔ Processing message queues ✔ Batch processing ✔ File uploads / downloads ✔ Real-time data pipelines
Large companies use it in:
- microservices
- backend APIs
- message processing systems
- high-throughput servers
Key Takeaways
- Creating threads manually does not scale.
- ThreadPoolExecutor manages worker threads efficiently.
- BlockingQueue stores tasks safely between producers and workers.
- ThreadFactory allows customization of thread creation.
- Together they provide scalable concurrency control in Java systems.
Understanding these components is essential for building high-performance backend systems.
👉 If you found this article helpful, follow me here on Medium and also don’t forget to clap and comment your thoughts. Connect with me on LinkedIn
메타데이터
- post_id
- fca98478646f
- slug
- stop-creating-threads-manually-master-threadpoolexecutor-threadfactory-and-blockingqueue-in-java-fca98478646f
- url
- https://levelup.gitconnected.com/stop-creating-threads-manually-master-threadpoolexecutor-threadfactory-and-blockingqueue-in-java-fca98478646f
- canonical_url
- https://levelup.gitconnected.com/stop-creating-threads-manually-master-threadpoolexecutor-threadfactory-and-blockingqueue-in-java-fca98478646f
- author_url
- https://medium.com/@balakrishna-02
- status
- ok
- fetched_at
- 2026-08-02 03:37:55