In this tutorial, we will delve into the Java Executor framework and understand how to leverage it for thread pooling.
Thread pooling is a powerful concept that optimizes the use of system resources by reusing existing threads rather than continuously creating new ones. By the end of this tutorial, you will learn how to manage and control threads effectively in a multi-threaded environment.
Prerequisites: Basic knowledge of Java programming and understanding of threads.
In Java, the Executor framework is a powerful tool that allows you to manage and control thread execution in concurrent Java applications. The Executor framework provides a pool of threads, which is a group of worker threads that are waiting for tasks to execute.
How does thread pool work?
When a new task is submitted to the thread pool, it will try to use a dormant (idle) thread in the pool if available. If all threads are active, the task will wait in a queue. As soon as a thread becomes idle, it can pick up a task from the queue and start executing it.
Here's how you can create a simple thread pool using Executor framework:
ExecutorService executorService = Executors.newFixedThreadPool(10);
In this example, we're creating a fixed-size thread pool of 10 threads. The newFixedThreadPool
method returns an instance of ExecutorService
, which can be used to manage the threads.
Example 1: Basic Thread Pool
Here's a simple example of using a thread pool to execute tasks:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new WorkerThread("" + i);
executorService.execute(worker);
}
executorService.shutdown();
while (!executorService.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
class WorkerThread implements Runnable {
private String command;
public WorkerThread(String s){
this.command=s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" Start. Command = "+command);
processCommand();
System.out.println(Thread.currentThread().getName()+" End.");
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In the above example, we have a pool of 5 threads. We have 10 tasks to execute. Once we submit these tasks to the executor, it starts executing them using the threads in the pool.
Expected Output:
pool-1-thread-1 Start. Command = 0
pool-1-thread-2 Start. Command = 1
pool-1-thread-3 Start. Command = 2
pool-1-thread-4 Start. Command = 3
pool-1-thread-5 Start. Command = 4
pool-1-thread-1 End.
pool-1-thread-6 Start. Command = 5
pool-1-thread-2 End.
pool-1-thread-7 Start. Command = 6
pool-1-thread-3 End.
pool-1-thread-8 Start. Command = 7
pool-1-thread-4 End.
pool-1-thread-9 Start. Command = 8
pool-1-thread-5 End.
pool-1-thread-10 Start. Command = 9
Finished all threads
In this tutorial, we learned how to use the Executor framework in Java to create a thread pool. We also learned how to submit tasks to the thread pool and control the execution of threads.
For further learning, check out the Java documentation on ExecutorService.
Exercise 1: Create a thread pool with a single thread and submit five tasks to it. Observe the order of execution.
Exercise 2: Create a thread pool with five threads. Submit tasks that have different sleep times and observe the order of execution.
Exercise 3: Modify the above program to use Callable
and Future
to get the result of computation.
Tips for further practice: Try to play with different types of thread pools provided by the Executors class like newCachedThreadPool()
, newSingleThreadExecutor()
, etc.