← Back to Multithreading Course

Class 28: Thread Pools and Fixed Thread Pools

Part 5 — Managing Threads with the Executor Framework

In the previous class, we learned about Executor and ExecutorService. We also saw how we can submit tasks without manually creating and starting threads.

Now let's understand an important concept used by the Executor Framework: Thread Pools.

1. What Is a Thread Pool?

Imagine a restaurant with five chefs.

Customers keep placing orders throughout the day. Instead of hiring a new chef for every order, the restaurant uses its existing team of five chefs.

When an order arrives, an available chef takes it. If all five chefs are busy, the new order waits until a chef becomes available.

A Thread Pool works in a similar way.

A Thread Pool is a group of worker threads that are created and managed for executing tasks.

Instead of creating a new thread for every task, we can reuse the threads available in the pool.

Think of it this way

We have a group of worker threads.

↓

We submit tasks to the pool.

↓

Available threads execute those tasks.

↓

After finishing a task, a thread can execute another task.

2. Why Do We Need Thread Pools?

Let's say our application receives 100 tasks.

One approach is to create 100 threads, one for each task. But this can consume unnecessary system resources.

Instead, we could create a pool of five worker threads.

These five threads can execute the 100 tasks by taking on additional work as they become available.

Thread pools offer several benefits:

Remember, a thread pool does not make tasks execute faster automatically. It gives us a more organized way to manage concurrent work.

3. What Is a Fixed Thread Pool?

Java provides a simple way to create a thread pool with a fixed number of worker threads.

It is called a Fixed Thread Pool.

A fixed thread pool maintains a specified number of worker threads. When all those threads are busy, additional submitted tasks wait in a queue until a worker becomes available.

For example, if we create a fixed thread pool with three threads:

4. Creating a Fixed Thread Pool in Java

Java provides the Executors utility class. We can use its newFixedThreadPool() method to create a fixed thread pool.

ExecutorService executorService =
        Executors.newFixedThreadPool(3);

Here, we are creating a thread pool with three worker threads.

The number 3 represents the number of worker threads in this pool.

5. Our First Fixed Thread Pool Example

Let's write a complete program.

We will create a fixed thread pool with three threads and submit six tasks.

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

public class FixedThreadPoolExample {

    public static void main(String[] args) {

        ExecutorService executorService =
                Executors.newFixedThreadPool(3);

        for (int i = 1; i <= 6; i++) {

            int taskNumber = i;

            executorService.execute(() -> {

                System.out.println(
                    "Task " + taskNumber +
                    " started by " +
                    Thread.currentThread().getName()
                );

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }

                System.out.println(
                    "Task " + taskNumber + " completed"
                );
            });
        }

        executorService.shutdown();
    }
}

Let's understand the important parts.

Step 1: Create the Thread Pool

ExecutorService executorService =
        Executors.newFixedThreadPool(3);

We create a pool with three worker threads.

Step 2: Submit Six Tasks

for (int i = 1; i <= 6; i++) {

    int taskNumber = i;

    executorService.execute(() -> {
        // Task logic
    });
}

We submit six tasks to the pool.

But remember, the pool has only three worker threads.

Therefore, at most three of these tasks can execute concurrently using this pool.

Step 3: Identify the Worker Thread

Thread.currentThread().getName()

We use this expression to find the name of the thread currently executing the task.

This helps us observe that different tasks are being executed by the worker threads managed by the pool.

Step 4: Simulate Some Work

Thread.sleep(2000);

We pause the current worker thread for two seconds. This is just to simulate a task that takes some time.

Notice that the thread is occupied while it sleeps. Another task will need to wait for an available worker.

Step 5: Shut Down the ExecutorService

executorService.shutdown();

We tell the ExecutorService that no more tasks will be submitted. The already submitted tasks are still allowed to complete.

We will understand shutdown and other lifecycle methods in detail in Class 29.

6. Understanding How the Six Tasks Are Executed

We have six tasks and three worker threads. Let's imagine how the execution may happen.

Initially

Worker 1 → Task 1

Worker 2 → Task 2

Worker 3 → Task 3

Tasks 4, 5 and 6 are waiting.

After Some Tasks Finish

Worker 1 → Task 4

Worker 2 → Task 5

Worker 3 → Task 6

This is a simplified illustration. The actual order in which tasks start and finish may vary.

The important thing is that the pool reuses its worker threads to execute multiple tasks.

7. Are the Same Threads Reused?

Yes!

One of the main advantages of a thread pool is thread reuse.

A worker thread can finish one task and then pick up another task from the queue.

We do not need to create a new Thread object for every task.

Also, remember that a fixed thread pool maintains its configured number of worker threads as tasks are submitted and executed.

8. Important: Fixed Thread Pool Does Not Mean Fixed Number of Tasks

This is a common point of confusion.

When we write:

Executors.newFixedThreadPool(3);

The number three represents the number of worker threads, not the number of tasks.

We can submit many more than three tasks. The tasks that cannot be picked up immediately wait in the executor's work queue.

The standard fixed thread pool created by this factory method uses an unbounded shared queue. This means the number of queued tasks is not limited by the pool size.

So, a fixed thread pool limits the number of worker threads, but it does not automatically limit how many tasks can be queued.

9. When Should We Use a Fixed Thread Pool?

A fixed thread pool can be useful when we want to execute multiple tasks concurrently but also want to control the number of worker threads.

For example:

The appropriate pool size depends on the nature of the work and the resources available to the application.

We will keep things simple for now. The important concept is understanding how a fixed thread pool works.

10. What We Learned Today

We learned that a thread pool is a group of reusable worker threads that execute submitted tasks.

We also created a fixed thread pool using:

Executors.newFixedThreadPool(3);

In our example, three worker threads handled six submitted tasks. Instead of creating six separate threads ourselves, we allowed the pool to manage the execution.

In the next class, we will focus on an important responsibility: shutting down an ExecutorService properly.

Test Your Understanding

Let's see how well you understood this class!

Question 1: What is a Thread Pool?

Question 2: Which method creates a fixed thread pool?

Question 3: If we create a fixed thread pool with three threads, how many tasks can execute concurrently using its worker threads?

Question 4: What happens when all worker threads are busy and another task is submitted to a standard fixed thread pool?

Question 5: What is one major advantage of using a thread pool?

Class Summary

Let's quickly revise what we learned today.

  • A Thread Pool is a group of worker threads managed to execute tasks.
  • Worker threads can be reused for multiple tasks.
  • A Fixed Thread Pool maintains a specified number of worker threads.
  • We create one using Executors.newFixedThreadPool().
  • When all workers are busy, additional tasks wait in a queue.
  • The pool size limits concurrent worker execution, not the number of tasks we can submit.

Remember: A thread pool helps us reuse threads and control how many tasks execute concurrently.

Next Class: Shutting Down an ExecutorService Properly

We have learned how to create a thread pool and submit tasks. But what happens when our application has finished submitting all its work?

Should we leave the ExecutorService running? How do we stop it safely without losing submitted tasks?

In the next class, we will understand shutdown(), shutdownNow() and how to wait for tasks to finish.

Let's keep learning, one concept at a time!

0% Completed

You have completed 28 out of 46 classes.