Design (LLD) Thread Pool - Machine Coding

Features Required:
Thread Management:
Fixed number of threads.
Ability to execute tasks concurrently.
Graceful shutdown of threads.
Task Queue:
A blocking queue to hold tasks before they are executed.
Ability to handle tasks when all threads are busy.
Task Submission:
Interface for submitting tasks.
Support for both short and long-running tasks.
Thread Reusability:
- Reuse threads for executing multiple tasks to minimize overhead.
Graceful Shutdown:
- Ability to shutdown the thread pool gracefully, ensuring all tasks are completed.
Design Patterns Involved:
Singleton Pattern:
- Used to ensure only one instance of the thread pool is created, providing global access.
Factory Method Pattern:
- Used to create task objects and threads, encapsulating the instantiation logic.
Producer-Consumer Pattern:
- Implemented using a blocking queue where tasks are produced by the main thread and consumed by worker threads.
Thread Pool Pattern:
- Core pattern where a pool of threads is maintained to perform tasks concurrently.
Strategy Pattern:
- Can be used to manage different task scheduling algorithms (FIFO, LIFO, Priority, etc.).
Multiple DS/Algorithms Involved:
Task Scheduling:
FIFO (First-In-First-Out): Tasks are executed in the order they are submitted.
Priority-based: Tasks can be prioritized based on certain criteria.
Task Execution:
- Runnable Interface: Tasks implement the
Runnableinterface and are executed by worker threads.
- Runnable Interface: Tasks implement the
Blocking Queue Implementation:
- LinkedBlockingQueue: A thread-safe implementation of a blocking queue to manage tasks.
Code (Java):
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
// Singleton Pattern - ThreadPool Class
public class ThreadPool {
private static ThreadPool instance;
private final BlockingQueue<Runnable> taskQueue;
private final Thread[] workers;
private final AtomicBoolean isShutDownInitiated;
// Private Constructor for Singleton
private ThreadPool(int numberOfThreads) {
taskQueue = new LinkedBlockingQueue<>();
workers = new Thread[numberOfThreads];
isShutDownInitiated = new AtomicBoolean(false);
for (int i = 0; i < numberOfThreads; i++) {
workers[i] = new Worker(taskQueue, isShutDownInitiated);
workers[i].start();
}
}
// Singleton getInstance method
public static synchronized ThreadPool getInstance(int numberOfThreads) {
if (instance == null) {
instance = new ThreadPool(numberOfThreads);
}
return instance;
}
// Method to submit task
public void submitTask(Runnable task) {
if (!isShutDownInitiated.get()) {
taskQueue.offer(task);
}
}
// Method to shutdown the pool
public void shutdown() {
isShutDownInitiated.set(true);
for (Thread worker : workers) {
worker.interrupt();
}
}
}
// Worker Class implementing Runnable (Factory Method Pattern)
class Worker extends Thread {
private final BlockingQueue<Runnable> taskQueue;
private final AtomicBoolean isShutDownInitiated;
public Worker(BlockingQueue<Runnable> taskQueue, AtomicBoolean isShutDownInitiated) {
this.taskQueue = taskQueue;
this.isShutDownInitiated = isShutDownInitiated;
}
@Override
public void run() {
while (!isShutDownInitiated.get() || !taskQueue.isEmpty()) {
try {
Runnable task = taskQueue.take();
task.run();
} catch (InterruptedException e) {
if (isShutDownInitiated.get()) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
// Task Class implementing Runnable (Factory Method Pattern)
class Task implements Runnable {
private final String taskName;
public Task(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("Executing Task: " + taskName);
try {
Thread.sleep(1000); // Simulate task execution
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Task Completed: " + taskName);
}
}
// Main Class to test the ThreadPool implementation
public class ThreadPoolExample {
public static void main(String[] args) {
ThreadPool threadPool = ThreadPool.getInstance(3);
// Submitting tasks to ThreadPool
for (int i = 1; i <= 10; i++) {
Task task = new Task("Task-" + i);
threadPool.submitTask(task);
}
// Shutting down the thread pool
threadPool.shutdown();
}
}
Explanation:
Singleton Pattern:
- The
ThreadPoolclass follows the Singleton pattern, ensuring that only one instance of the thread pool is created and used globally.
- The
Factory Method Pattern:
- Both
WorkerandTaskclasses are instantiated using the factory method, which encapsulates the creation logic.
- Both
Producer-Consumer Pattern:
- The
LinkedBlockingQueueis used to hold tasks. Tasks are produced by the main thread and consumed by the worker threads.
- The
Thread Pool Pattern:
- The
ThreadPoolclass maintains a fixed pool of threads to execute tasks concurrently.
- The
Strategy Pattern (Optional):
- If you want to extend the design, you could implement different scheduling strategies for task execution.
This design is modular and can be easily extended to incorporate additional features like task prioritization, dynamic thread pool sizing, etc.
