DEV Community

Code Craft Club
Code Craft Club

Posted on • Originally published at Medium

Understanding Executors and Thread Pools in Java

The Car Manufacturing Factory Analogy

Imagine a car manufacturing factory with multiple production lines, each line representing a thread. These production lines work in parallel to assemble cars. However, there's a queue of customer orders that need to be processed.

Car Factory

The Challenge

  1. Overflow of Orders: The factory receives a constant stream of customer orders, all queued up. Each order needs to go through the assembly lines to be completed.
  2. One Order, One Line: Initially, the factory's approach is to assign each order to a separate production line. This means if there are too many orders, the factory must have an enormous number of production lines, which is impractical.
  3. Context Switching Chaos: With so many production lines, they keep switching back and forth between tasks, like changing tools on a workstation. This context switching is time-consuming and doesn't contribute directly to building cars.

Thread Pools to the Rescue

To address these issues, the factory introduces the concept of a thread pool:

1. Creating a Thread Pool

In Java, the factory installs a system that manages the production lines efficiently:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

ExecutorService threadPool = Executors.newFixedThreadPool(5);
Enter fullscreen mode Exit fullscreen mode

Here, they establish a thread pool with, say, five production lines (threads).

2. Efficient Order Handling

Now, when new customer orders arrive, they're sent to the thread pool for processing:

threadPool.execute();
Enter fullscreen mode Exit fullscreen mode

3. Efficient Use of Resources

The thread pool optimizes the production lines (threads) as follows:

  • It reuses the same production lines for different orders.
  • When one order (task) is complete, the production line (thread) becomes available for the next order.
  • This prevents the need to create and dismantle production lines for every order, reducing wasted time and effort.

In summary, just as a car manufacturing factory streamlined its production lines by using a thread pool, software applications benefit from thread pools and executors. Thread pools help manage resources efficiently, avoid excessive context switching, and ensure that tasks are processed in an organized and effective manner.

Implementation example of a Thread Pool

Here's an example of creating a task class (analogous to a car assembly task) and passing it to the thread pool using execute():

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class CarAssemblyTask implements Runnable {
    @Override
    public void run() {
        // Simulate car assembly process
        System.out.println("Car is being assembled on production line " + Thread.currentThread().getName());
    }
}

public class CarFactory {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 10; i++) {
            threadPool.execute(new CarAssemblyTask());
        }

        // Shutdown the thread pool when done
        threadPool.shutdown();
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we've created a CarAssemblyTask class that implements the Runnable interface. We then submit instances of this task class to the thread pool using execute(). Each task represents the assembly of a car on one of the production lines. The thread pool efficiently manages these tasks, and the production lines are reused for different orders.

To implement the Thread Pool, we have used newFixedThreadPool() in the above code that allows us to create a fixed number of threads inside a thread pool. Apart from using the above mentioned newFixedThreadPool(), Java also provides cached Thread Pool which is implemented as Executors.newCachedThreadPool().

What is a Cached Thread Pool?

This method creates a thread pool that dynamically adjusts the number of threads based on the workload. It's often used for tasks where the number of tasks is not known in advance, or tasks are short-lived.

Characteristics:

  • It starts with no threads and creates new threads as needed to accommodate incoming tasks.
  • If a thread becomes idle for a certain period (typically 60 seconds), it may be terminated to free up resources.
  • There is no fixed limit on the number of threads; it can grow as needed.

How to choose between Cached Thread Pool and Fixed Thread Pool?

  • Use newCachedThreadPool() when you have a highly variable workload, where the number of tasks can fluctuate widely, and you want the thread pool to adapt dynamically. This is common for short, bursty tasks.
  • Use newFixedThreadPool(n) when you want to restrict the concurrency to a specific number of threads, ensuring that you don't overload the system with too many threads. This is appropriate for tasks with a more predictable workload and when you want to control resource usage.

Closing Thoughts:

Thank you for reading our blog. We appreciate your time and interest in our content. If you have any questions, feedback, or topics you’d like us to cover in our future articles, please feel free to leave a comment below. Your input is valuable, and it helps us create content that matters to you.

Stay connected with us for more insightful articles on various topics related to technology, programming, and much more.

Top comments (0)