package
com.kartik.thread.pool; import
java.util.concurrent.ExecutorService; import
java.util.concurrent.Executors; /** * * @author kmandal * * */ public class
TestThreadPool { // Maximum number of threads in thread pool static final int MAX_T = 3; public static void main(String[] args) { // creates five tasks Task r1 = new Task("task
1",4); Task r2 = new Task("task
2",3); Runnable r3 = new Task("task
3",6); Runnable r4 = new Task("task
4",9); Runnable r5 = new Task("task
5",18); Runnable r6 = new Task("task
6",4); Runnable r7 = new Task("task
7",3); Runnable r8 = new Task("task
8",6); Runnable r9 = new Task("task
9",9); Runnable r10 = new Task("task
10",4); Runnable r11 = new Task("task
11",3); Runnable r12 = new Task("task
12",6); Runnable r13 = new Task("task
13",9); Runnable r14 = new Task("task
14",4); Runnable r15 = new Task("task
15",3); Runnable r16 = new Task("task
16",6); Runnable r17 = new Task("task
17",9); // creates a thread pool with MAX_T
no. of // threads as the fixed pool
size(Step 2) ExecutorService pool =
Executors.newFixedThreadPool(MAX_T); // passes the Task objects to the
pool to execute (Step 3) pool.execute(r1); pool.execute(r2); pool.execute(r3); pool.execute(r4); pool.execute(r5); pool.execute(r6); pool.execute(r7); pool.execute(r8); pool.execute(r9); pool.execute(r10); pool.execute(r11); pool.execute(r12); pool.execute(r13); pool.execute(r14); pool.execute(r15); pool.execute(r16); pool.execute(r17); // pool shutdown ( Step 4) pool.shutdown(); } } |
package
com.kartik.thread.pool; //Java
program to illustrate //ThreadPool import
java.text.SimpleDateFormat; import
java.util.Date; //Task class
to be executed (Step 1) public class
Task implements Runnable { private String name; private int data; public Task(String s,int d) { name = s; data=d; } // Prints task name and sleeps for 1s // This Whole process is repeated 5 times public void run() { try { System.out.println("Executing
Time for task name - "+name +" and factorial data = "
+factorial(data)+" thread name "+Thread.currentThread().getName()); Thread.sleep(100); System.out.println(name+"
complete"); } catch(InterruptedException e) { e.printStackTrace(); } } //method to
find factorial of given number static long factorial(long n) { if (n == 0) return 1; return n*factorial(n-1); } } |
TestThreadPool example effectively demonstrates the use of a
thread pool in Java. Let’s break down some of the concepts you're asking about
and explain why a thread pool offers better performance compared to creating
new threads each time.
Thread Life Cycle in Java
A thread in Java generally goes through three stages:
- Creation:
This involves notifying the operating system (OS) to create a new thread.
The OS allocates resources, such as a stack, for the thread.
- Execution:
The thread executes its run() method. This is where the actual work of the
thread is done.
- Termination:
After the thread finishes executing its run() method, it is terminated.
The OS then reclaims the resources allocated to it.
Thread Pool Basics
- Thread
Pool: A thread pool is a group of pre-created threads that are kept
alive and reused to execute multiple tasks. When a new task is submitted,
a thread from the pool is assigned to execute the task. After completing
the task, the thread goes back to the pool instead of being destroyed, so
it can be reused for future tasks.
- Worker
Threads: These are the threads that belong to the thread pool. Unlike
regular threads that are created, run, and then destroyed, worker threads
are kept alive and can execute multiple tasks throughout their lifecycle.
This is why they are more efficient—they avoid the overhead associated
with thread creation and destruction.
Why Thread Pools Improve Performance
- Reduced
Overhead: The most significant advantage of using a thread pool is the
reduction in overhead for creating and destroying threads. In scenarios
where tasks are short-lived or where many tasks need to be executed,
constantly creating and destroying threads can consume significant CPU and
memory resources.
- Efficient
Resource Management: Thread pools manage resources more efficiently by
limiting the number of threads that can run concurrently. This prevents
the system from being overwhelmed by too many threads.
- Reusability:
Threads in a pool are reused to execute multiple tasks. After a thread
completes a task, it does not terminate but instead goes back to the pool
to be reused for the next task.
Example: Understanding the Code
code example demonstrates these principles:
java
// Creates a
thread pool with a maximum of 3 threads ExecutorService
pool = Executors.newFixedThreadPool(MAX_T); // Submits
multiple tasks to the thread pool pool.execute(r1); pool.execute(r2); ... pool.execute(r17); // Shuts down
the pool after all tasks have been submitted pool.shutdown(); |
Key Points:
- Thread
Reuse: The same worker threads in the pool are reused for different
tasks. For example, a thread that completes task 1 can be immediately
reused to execute task 4 without being destroyed.
- Concurrency
Control: The thread pool ensures that only MAX_T (3 in this case)
threads are active at any time. If more tasks are submitted than there are
available threads, the tasks are queued and will be executed as threads
become available.
Worker Threads vs. Regular Threads
- Worker
Threads: Exist separately from the tasks they execute. They remain
alive in the pool and can execute multiple tasks over time.
- Regular
Threads: Are created for a specific task and usually destroyed after
the task is completed.
This code defines a Task class that implements the Runnable
interface. Each task calculates the factorial of a given number and prints out
some details, including the name of the task and the thread it is running on.
This setup is useful for executing tasks concurrently using a thread pool.
Here’s a brief explanation of the components:
1. Class Definition and Constructor
- The
class Task implements Runnable, meaning it can be executed by a thread.
- The
constructor accepts two parameters:
- String
s: The name of the task.
- int
d: The data on which the task will operate (in this case, the number for
which the factorial is to be calculated).
2. run() Method
- This
is the method that will be executed when the thread runs. It performs the
following:
- Prints
out the task's name, the factorial of the number, and the name of the
current thread.
- Sleeps
for 100 milliseconds to simulate some work being done.
- Prints
a completion message after waking up.
3. factorial() Method
- A
static method that calculates the factorial of a given number using
recursion.
Sample Output
When run, the run() method of the Task class might produce
output similar to the following:
plaintext
Executing
Time for task name - Task1 and factorial data = 120 thread name
pool-1-thread-1 Task1
complete Executing
Time for task name - Task2 and factorial data = 720 thread name
pool-1-thread-2 Task2
complete |
How to Use with a Thread Pool
To make use of this Task class, you can create a thread pool
using Java's ExecutorService. Here’s a basic example of how to set it up:
java
package
com.kartik.thread.pool; import
java.util.concurrent.ExecutorService; import
java.util.concurrent.Executors; public class
ThreadPoolExample { public static void main(String[] args) { // Creating a thread pool with 3
threads ExecutorService executor =
Executors.newFixedThreadPool(3); // Submitting tasks to the thread
pool for (int i = 1; i <= 5; i++) { Task task = new
Task("Task" + i, i + 4); executor.execute(task); } // Shutting down the executor service executor.shutdown(); } } |
Explanation of the Thread Pool Example:
- ExecutorService:
Manages a pool of threads, executing tasks concurrently.
- Executors.newFixedThreadPool(3):
Creates a thread pool with 3 threads.
- Submitting
Tasks: The loop creates and submits 5 tasks to the thread pool. Each
task calculates the factorial of a number and prints out the result.
- Shutdown:
Once all tasks are submitted, executor.shutdown() is called to stop
accepting new tasks and let the existing ones finish.
Running the Example
When you run ThreadPoolExample, the tasks will execute
concurrently, each on a separate thread from the pool. The number of concurrent
threads will not exceed the size of the pool (3 in this case), so some tasks
may wait for others to finish before starting.
This setup is commonly used for managing multiple tasks that
can be processed in parallel, especially in scenarios involving I/O operations,
long computations, or tasks that are independent of each other.
When to Use Thread Pools
Thread pools are ideal when:
- You
have a large number of short-lived tasks.
- You
need to control the number of concurrent threads.
- You
want to avoid the overhead of creating and destroying threads repeatedly.
By using a thread pool, you achieve better performance,
especially in environments where tasks are numerous and frequent.
Executor Service |
0 Comments