Design Custom Thread Pool (Java)

vipul pachauri
3 min readApr 18, 2023

--

What is Thread Pool ?

A thread pool is a group of pre-initialized worker threads that are managed by a thread pool manager. The thread pool manager is responsible for distributing tasks to the worker threads and managing the execution of the tasks.

Instead of creating a new thread for every task, which can be inefficient and can cause resource contention, a thread pool allows for a set of threads to be created once and then reused for multiple tasks. This can improve the performance and scalability of an application.

Design Thread Pool : -

To design a thread pool we need following entities :

  1. Thread Pool : Enqueue the task in blocking queue
  2. Blocking Queue : Stores the tasks
  3. Task Executor : Execute the tasks

Thread Pool :

  1. Thread Pool class will initialise the blocking queue size and no of threads available in pool via constructor.
  2. In constructor we will create given no of threads and pass the task executor (Runnable interface) reference to thread and start them.
  3. Thread Pool class has submit(Runnable runnable) method to enqueue the task in blocking queue.
package LLD.customThreadPool;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

public class ThreadPool {

private BlockingQueue<Runnable> blockingQueue;

// Define size of queue and no of threads
public ThreadPool(int queueSize, int noOfThreads) {
blockingQueue = new LinkedBlockingDeque<>(queueSize);
TaskExecutor taskExecutor = null;
for(int i =0;i<noOfThreads;i++){
taskExecutor = new TaskExecutor(blockingQueue);
Thread thread = new Thread(taskExecutor);
thread.start();
}
}

public void submit(Runnable runnable){
blockingQueue.add(runnable);
}
}

Task Executor :

  1. Task Executor is a Runnable interface, which is responsible to fetch the task from blocking queue and execute them.
public class TaskExecutor implements Runnable {

private BlockingQueue<Runnable> blockingQueue;

public TaskExecutor(BlockingQueue<Runnable> blockingQueue) {
this.blockingQueue = blockingQueue;
}

@Override
public void run() {
while (true){
try {
// take the task from queue and execute
Runnable runnable = blockingQueue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

Now, lets test our thread pool :

  1. To test our thread pool we will be creating few test task and submit to ur thread pool. Test Task is thread only.
public class TestThreadPool {

public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(3,2);
for(int taskNumber = 1 ; taskNumber <= 7; taskNumber++) {
TestTask testTask1 = new TestTask("abcd_"+taskNumber);
threadPool.submit(testTask1);
}


}
}

One point I would like to add that why we are using Blocking Queue to hold the task ?

So the answer is very simple, The BlockingQueue allows threads to wait for new tasks when the queue is empty and only accepts new tasks when space is available, preventing the system from being overloaded with too many tasks at once.

I hope this will help you to understand thread pool internal working as well. Happy Learning !!

--

--

vipul pachauri
vipul pachauri

Written by vipul pachauri

Senior Software Backend Engineer

No responses yet