This content originally appeared on Level Up Coding - Medium and was authored by Corey Duffy
Maximising Efficiency, Improving Scalability, and Simplifying Code with Thread Pools in Java
One of the most effective, and efficient ways to implement multi-threading in Java is through the use of thread pools.
What are thread pools and how do they work?
In simple terms, a thread pool is a group of pre-initialised threads that can execute a set of tasks. What makes thread pools interesting is how they allow for the reusing of threads. i.e. instead of creating and destroying threads every time a new task is submitted, a thread pool keeps a set of threads ready and waiting for reuse.
Underneath the hood, here’s what’s going on:
- A specified number of threads are created and initialised in the thread pool.
- When a new task needs to be executed, it is added to a queue.
- If any thread from the thread pool is available, it will be assigned to execute the next task in the queue.
- Once the task is completed, the thread is returned to the thread pool, where it becomes available to execute the next task.
Getting to the code
So, that’s how thread pools work in theory, but let’s see how that looks when we code it out.
First, let’s take a look at how we might run 5 tasks using 5 different threads, without the use of a thread pool:
public class Main {
public static void main(String[] args) {
int numberOfTasks = 10;
for (int i = 1; i <= numberOfTasks; i++) {
Thread thread = new Thread(new ExampleTask(i));
thread.start();
}
}
}
final class ExampleTask implements Runnable {
private int taskNumber;
public ExampleTask(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public void run() {
System.out.println("Hello there, I'm executing task number: " + taskNumber);
}
}
In the code above, in theNonThreadPoolExample class, we create 5 tasks by creating a new Thread object for each task and passing an instance of ExampleTask to its constructor. Then, we call the start() method on each of these threads to start executing its corresponding task. This will give us an output like the below:
Hello there, I'm executing task number: 6
Hello there, I'm executing task number: 5
Hello there, I'm executing task number: 8
Hello there, I'm executing task number: 3
Hello there, I'm executing task number: 10
Hello there, I'm executing task number: 7
Hello there, I'm executing task number: 1
Hello there, I'm executing task number: 2
Hello there, I'm executing task number: 4
Hello there, I'm executing task number: 9
This will work, but it’s an inefficient implementation of what we’re trying to achieve. Here, we’re creating and destroying threads for each new task that we want to run. This process can be cumbersome and can easily lead to performance issues if we’re not careful.
So, how else could we do this? How would we implement a thread pool?
Let’s take a look at the example below:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 10; i++) {
executor.submit(new ExampleTask(i));
}
// Shutdown the thread pool once all tasks are complete
executor.shutdown();
}
}
final class ExampleTask implements Runnable {
private int taskNumber;
public ExampleTask(int taskNumber) {
this.taskNumber = taskNumber;
}
@Override
public void run() {
System.out.println("Hello there, I'm executing task number: " + taskNumber);
}
}
In this implementation, we’ve set up an ExecutorService and created a thread pool with 5 threads by using the newFixedThreadPool() method from the Executors class. This essentially pre-initialises 5 threads which we can use and reuse throughout our code. We then submit a total of 10 tasks to the thread pool using the submit() method.
When a task is submitted to the thread pool, if a thread is available, it will be assigned to execute the submitted task. Once the task is complete, the thread is returned to the thread pool and becomes available to execute the next task. Thus, we don’t need to create and tear down threads for each task that we want to run. In the example above, the same 5 threads are used to run the 10 submitted tasks, resulting in the output below:
Hello there, I'm executing task number: 3
Hello there, I'm executing task number: 5
Hello there, I'm executing task number: 4
Hello there, I'm executing task number: 1
Hello there, I'm executing task number: 2
Hello there, I'm executing task number: 6
Hello there, I'm executing task number: 8
Hello there, I'm executing task number: 7
Hello there, I'm executing task number: 10
Hello there, I'm executing task number: 9
Importance of the shutdown method
Finally, we call the shutdown() method on the thread pool to gracefully terminate all threads once all tasks are complete. This can easily be missed when you first start making use of thread pools.
If theshutdown() method isn’t called, the thread pool will continue to run in the background, even after all tasks have been completed. Essentially, this means that your application will use more resources than it needs to and can lead to unexpected results.
The shutdown() method will initiate the process of shutting down the thread pool gracefully. Thus, the thread pool will stop accepting new tasks and it will wait for all submitted tasks to complete, before going on to terminate all threads in the pool. Once the shutdown() method is called, you cannot submit any new tasks to the thread pool.
Alternatively, you can also use the shutdownNow() method to force the thread pool to stop immediately, but this may result in some tasks being cancelled before they can complete.
What are the benefits of using a thread pool?
To summarise, thread pools offer a number of benefits, such as:
- Improved performance: Thread pools can reuse threads instead of creating and destroying them for each task, reducing overhead and making more efficient use of resources.
- More control to manage resources: By limiting the number of threads in the pool, you can manage the resources available to your application, preventing it from becoming overwhelmed with too many threads.
- Greater control over your task execution: Thread pools allow you to control how many tasks are executed simultaneously, along with how many are queued. This can help prevent your application from overloading a resource or running out of memory.
- Simplifying code: Thread pools can help make your code easier to read and maintain by hiding the details of thread creation, management and deletion.
- Enhancing scalability: The way in which thread pools reuse threads will allow you to manage a large number of requests without running out of system resources.
In conclusion, thread pools provide an efficient and easy-to-implement way to manage threads in your Java applications. By reusing threads instead of creating and destroying them for each task, thread pools can improve the efficiency of thread management and prevent performance issues. With these benefits in mind, thread pools are an essential tool for any Java developer looking to improve the efficiency and scalability of their concurrent applications.
Thanks very much for taking the time to read this. Hopefully, it’s been of some help to you in understanding how to make use of thread pools in your applications.
If you enjoyed this or found it informative, please consider reading some of my other articles or following me on Medium. Cheers!
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 💰 Free coding interview course ⇒ View Course
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job
Optimising Java Performance: Understanding Thread Pools and Their Benefits was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Corey Duffy
Corey Duffy | Sciencx (2023-02-20T03:33:25+00:00) Optimising Java Performance: Understanding Thread Pools and Their Benefits. Retrieved from https://www.scien.cx/2023/02/20/optimising-java-performance-understanding-thread-pools-and-their-benefits/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.