Recycle Threads and Save Resources
Before we begin to talk about thread pool, let revisit threads. Threads are really powerful things. By creating multiple threads, multithreading, you can do ...
Join the DZone community and get the full member experience.
Join For FreeHey, Tea Lovers! Before we begin to talk about thread pool, lets revisit threads. Threads are really powerful things. By creating multiple threads, multithreading, you can do tasks simultaneously. Wow, how cool and how efficient, right? But wait, if there is a heaven, there is a hell. Multithreading does come with thread management overhead for the developer, such as creating multiple threads, the synchronicity between them, deadlock, and most importantly, understanding how efficient it really is.
In this post, we will talk about how multithreading affects your system without you knowing it, how to overcome certain parts of it, and how to make it more efficient, including thread reuse or recycling. If you want to learn about multithreading in Java or want to refresh the basics you can see my previous post "How MultiThreading Lets You Drive A Car, Ride The Bikes, And Sail A Boat Simultaneously" [here].
The Hell
An efficient and super fast program is the dream of every programmer. To make this happen, we usually end up using threads, multithreading precisely. Most of the time we create so many threads that we are actually decreasing the speed of our program instead of making it faster. How? Creating so many threads is a very expensive process, and it can delay the actual process which you wanted to avoid in the first place.
In Java, threads are mapped to system-level threads, so over numbering them can empty your resources. Once the run()
method finishes, we never use that thread again and end up creating new threads again and again. Resulting in thread creation overhead. And there are other factors as well, such as your CPU. If we end up with too many threads that our COU, most of the time gets spent in context switching. So, how will you avoid these multiple new thread creation? Just sip your tea and read. For the code, you can find it on GitHub or the full project here.
Recycling Is Good for the Environment: Thread Pool
Recycling is good for our environment since the resources are limited on our planet.
Similarly, our computer resources are also limited. So, why not reuse them and save efficiency? Reuse the thread we used already instead of creating a new one?
How Does Thread Pooling or Any Pooling Works
What is pooling by the way? In a simpler word, combining resources for sharing. Examples can be your Uber pool, printer sharing in your office, Dynamic Host Configuration Protocol (DHCP), JDBC connection pooling, and many more. I have explained pooling in more detailed in How to Achieve Greatness in JDBC Performance [here] and JDBC Connection Pooling Explained with HikariCP [here].
In thread pooling or any pooling, you collect N number of threads or resource in one place. You pick one thread from it, use it, and put it back into the pool after completing your work. Well, it's the simplest way of explaining it. There can be certain scenarios like all the resources are busy when you went to pick it up, you wait or add one more resource if possible. These are the basic conditions and scenarios of any pooling system.
Thread Pool Continued
Now that we know about the pooling, let us jump to the actual working of thread pooling in Java. To use the Threadpool in Java, Java provides a framework called Executor Framework, which is an interface called Executor
and its subinterface ExecutorService
and the classes implementing them. Don't worry, it's not another dependency. It is in the package java.util.concurrent
. The Executor
contains only one abstract method called execute(Runnable task)
. Through this, you can execute your tasks. Tasks are just Runnable
objects.
xxxxxxxxxx
public interface Executor {
void execute(Runnable command);
}
Thread Pool ExecutorService
ExecutorService is a subinterface of the Executor
interface. It is a pro version you can say. It has multiple methods that give us more control over tasks. To execute a task, you can use the execute(Runnable task)
method. There is a method submit(..)
that works similarly to execute
, but returns a Future<T>
, which, as the name suggests, gives you a future value. Future
basically says that "I may or may not have the value right now, but it could be available in the future if the task is completed and produces some result". It deserves its own post, and we will do that in future update here. But, how can you create the object or thread pool of ExecutorService
? Via Executors
class.
Thread Pool Executors
Executors
is a kind of factory/utility class for the ExecutorService
. It creates the objects of ExecutorService
. There are various ways you can create different types of pools.
Method | Description |
---|---|
newSingleThreadExecutor() | Creates a single thread. |
newFixedThreadPool(int size) | Creates a fixed size thread pool. |
newCachedThreadPool() | Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads if they are available |
In the case of newFixedThreadPool(int size)
and newSingleThreadExecutor()
, the threads need to wait if the size is full and no thread is available to use. But in the case of newCachedThreadPool()
, a new task doesn't need to wait if the thread is not available.
Make sure to use
shutdown()
after all the task has been assigned or the program will not stop as the thread pool will still be active.
Understand Java ThreadPool With Examples
Let us rewrite the example from "How MultiThreading Lets You Drive A Car, Ride The Bikes, And Sail A Boat Simultaneously" using the thread pool. In the example, you have boats, cars, and bikes. In the previous example, we created a new thread for each vehicle. Now, we will utilize our new understanding of thread pools.
The Tasks: Car, Bike, and Boat
xxxxxxxxxx
public class ThreadPool {
static void pring5Times(String statement) {
for (int i = 0; i < 5; i++) {
System.out.println(statement + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Car1 implements Runnable {
public void run() {
ThreadPool.pring5Times("I am driving a Car on road ");
}
}
class Bike1 implements Runnable {
public void run() {
ThreadPool.pring5Times("I am riding a Bike on road ");
}
}
class Boat1 implements Runnable {
public void run() {
ThreadPool.pring5Times("I am Sailing the boat on sea ");
}
}
These are the Runnable
objects we will be using. And there are one utility function pring5Times(String statement) to print the given statement 5 times. Let us see each function one-by-one.
Executors.newSingleThreadExecutor()
It creates only one thread in the pool, and each task needs to wait for the previous one to complete to use the thread. In this, sequential execution takes place.
xxxxxxxxxx
static void newSingleThreadExecutor() {
System.out.println("Running Executors.newSingleThreadExecutor()");
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(new Car1());
/* bike and boat will wait till car driving is completed. */
singleThreadPool.execute(new Bike1());
singleThreadPool.execute(new Boat1());
/* make sure to shitdown. */
singleThreadPool.shutdown();
}
Output
xxxxxxxxxx
Running Executors.newSingleThreadExecutor()
I am driving a Car on road 0
I am driving a Car on road 1
I am driving a Car on road 2
I am driving a Car on road 3
I am driving a Car on road 4
I am riding a Bike on road 0
I am riding a Bike on road 1
I am riding a Bike on road 2
I am riding a Bike on road 3
I am riding a Bike on road 4
I am Sailing the boat on sea 0
I am Sailing the boat on sea 1
I am Sailing the boat on sea 2
I am Sailing the boat on sea 3
I am Sailing the boat on sea 4
As you can see, threads are running one-by-one, since there is only one thread to be shared.
Executors.newFixedThreadPool(int limit)
This creates a poll with the given number of threads i.e limit
. The number of tasks that will run in parallel is the given limit. Other task needs to wait for a thread to complete. So, in our example, we have created a pool with size 2. So, Car
and Bike
will run in parallel or simultaneously, but the Boat needs to wait for either of them to complete.
xxxxxxxxxx
static void newFixedThreadPool(){
System.out.println("Running Executors.newFixedThreadPool(2)");
ExecutorService fixedSizeThreadPool = Executors.newFixedThreadPool(2);
fixedSizeThreadPool.execute(new Car1());
fixedSizeThreadPool.execute(new Bike1());
/* this time Since threadpool only has 2 thread,
Boat will wait till car driving and bike riding is completed. */
fixedSizeThreadPool.execute(new Boat1());
fixedSizeThreadPool.shutdown();
}
Output
xxxxxxxxxx
Running Executors.newFixedThreadPool(2)
I am driving a Car on road 0
I am riding a Bike on road 0
I am driving a Car on road 1
I am riding a Bike on road 1
I am driving a Car on road 2
I am riding a Bike on road 2
I am driving a Car on road 3
I am riding a Bike on road 3
I am driving a Car on road 4
I am riding a Bike on road 4
I am Sailing the boat on sea 0
I am Sailing the boat on sea 1
I am Sailing the boat on sea 2
I am Sailing the boat on sea 3
I am Sailing the boat on sea 4
Bike and Car ran simultaneously, but the boat needed to wait since only 2 threads were available.
Executors.newCachedThreadPool()
This method creates a unique and dynamic pool. If the thread is available, it gets assigned to the task. If not, a thread is added in the pool. In this, a thread does not wait for other threads to complete. The best suitable scenario would be when you have small tasks that need to be run simultaneously.
The Tasks are the same as the above example but this time all three of the tasks, Car
, Bike
, and Boat
would run simultaneously.
xxxxxxxxxx
static void newCachedThreadPool(){
System.out.println("Running Executors.newCachedThreadPool()");
ExecutorService chachedThreadPool = Executors.newCachedThreadPool();
chachedThreadPool.execute(new Car1());
chachedThreadPool.execute(new Bike1());
chachedThreadPool.execute(new Boat1());
chachedThreadPool.shutdown();
}
Output
xxxxxxxxxx
Running Executors.newCachedThreadPool()
I am driving a Car on road 0
I am riding a Bike on road 0
I am Sailing the boat on sea 0
I am driving a Car on road 1
I am riding a Bike on road 1
I am Sailing the boat on sea 1
I am driving a Car on road 2
I am riding a Bike on road 2
I am Sailing the boat on sea 2
I am driving a Car on road 3
I am riding a Bike on road 3
I am Sailing the boat on sea 3
I am driving a Car on road 4
I am riding a Bike on road 4
I am Sailing the boat on sea 4
As you can see, you were able to drive, ride, and sail simultaneously.
Conclusion
We have looked into the bad side of multithreading and its overhead on the system, what is thread pool and how to use them, type of thread pool. I hope you enjoyed the post. In the next post on concurrency, we will talk about Future
and CompletableFuture
. You can find the code on GitHub here or the full project here. You may also like, TV Series Every Programmer Should Watch, Reduce the null pointer with the help of Optional and say goodbye to boilerplate code with Lombok.
Published at DZone with permission of Imran Shaikh. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments