Loading...

12-Hour Money-Back Guarantee

Design (LLD) Thread Pool - Machine Coding

Design (LLD) Thread Pool - Machine Coding

Design (LLD) Thread Pool - Machine Coding

5 Aug 20246 min read

Features Required:

  1. Thread Management:

    • Fixed number of threads.

    • Ability to execute tasks concurrently.

    • Graceful shutdown of threads.

  2. Task Queue:

    • A blocking queue to hold tasks before they are executed.

    • Ability to handle tasks when all threads are busy.

  3. Task Submission:

    • Interface for submitting tasks.

    • Support for both short and long-running tasks.

  4. Thread Reusability:

    • Reuse threads for executing multiple tasks to minimize overhead.
  5. Graceful Shutdown:

    • Ability to shutdown the thread pool gracefully, ensuring all tasks are completed.

Design Patterns Involved:

  1. Singleton Pattern:

    • Used to ensure only one instance of the thread pool is created, providing global access.
  2. Factory Method Pattern:

    • Used to create task objects and threads, encapsulating the instantiation logic.
  3. Producer-Consumer Pattern:

    • Implemented using a blocking queue where tasks are produced by the main thread and consumed by worker threads.
  4. Thread Pool Pattern:

    • Core pattern where a pool of threads is maintained to perform tasks concurrently.
  5. Strategy Pattern:

    • Can be used to manage different task scheduling algorithms (FIFO, LIFO, Priority, etc.).

Multiple DS/Algorithms Involved:

  1. 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.

  2. Task Execution:

    • Runnable Interface: Tasks implement the Runnable interface and are executed by worker threads.
  3. 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:

  1. Singleton Pattern:

    • The ThreadPool class follows the Singleton pattern, ensuring that only one instance of the thread pool is created and used globally.
  2. Factory Method Pattern:

    • Both Worker and Task classes are instantiated using the factory method, which encapsulates the creation logic.
  3. Producer-Consumer Pattern:

    • The LinkedBlockingQueue is used to hold tasks. Tasks are produced by the main thread and consumed by the worker threads.
  4. Thread Pool Pattern:

    • The ThreadPool class maintains a fixed pool of threads to execute tasks concurrently.
  5. 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.