0% found this document useful (0 votes)
3 views31 pages

Multi Threading

Multithreading is a programming concept that allows concurrent execution of multiple threads within a single process, enhancing performance and efficiency. Key concepts include the differences between processes and threads, the thread life cycle, and the benefits of multithreading such as improved responsiveness and resource sharing. Java provides various methods for defining threads, managing their names and priorities, and ensuring synchronization to prevent concurrent access issues.

Uploaded by

likhardev098
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views31 pages

Multi Threading

Multithreading is a programming concept that allows concurrent execution of multiple threads within a single process, enhancing performance and efficiency. Key concepts include the differences between processes and threads, the thread life cycle, and the benefits of multithreading such as improved responsiveness and resource sharing. Java provides various methods for defining threads, managing their names and priorities, and ensuring synchronization to prevent concurrent access issues.

Uploaded by

likhardev098
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 31

Multi Threading

Introduction:-
Multithreading is a programming concept that allows a process to create and manage multiple
threads of execution concurrently within the same program. It enables tasks to run in parallel,
improving the overall performance and efficiency of a program by utilizing the full power of multi-
core processors.

Key Concepts

1. Thread: A thread is the smallest unit of a process that can be scheduled by the
operating system. Each thread in a process shares the same memory space but can
execute independently.
2. Process vs Thread:
o A process is an instance of a program that runs in its own memory space.
o A thread is a subset of a process, and multiple threads can exist within the
same process, sharing resources like memory and file handles.
3. Concurrency vs Parallelism:
o Concurrency is when multiple tasks make progress within overlapping time
periods. It doesn’t necessarily mean they are running at the same time.
o Parallelism is when multiple tasks are executed simultaneously on different
processors or cores.
4. Thread Life Cycle:
o New: Thread is created but not yet started.
o Runnable: Thread is ready to run and waiting for CPU time.
o Running: Thread is actively executing instructions.
o Blocked/Waiting: Thread is waiting for a resource to become available.
o Terminated: Thread has finished execution.

Benefits of Multithreading

 Improved Performance: Increases CPU utilization by performing multiple


operations at the same time.
 Resource Sharing: Threads can share the memory and resources of the parent
process, which can be more efficient than using multiple processes.
 Responsiveness: Allows programs to remain responsive (e.g., in a user interface)
while performing background tasks.

Use Cases

 Web Servers: Handle multiple client requests concurrently.


 Real-time applications: Such as gaming, where multiple tasks (like rendering, input
processing) need to occur simultaneously.
 Parallel computations: For speeding up tasks like matrix operations, data processing,
etc.
The ways to define a thread :-

In Java, a thread can be defined in multiple ways. Here are the two most common
approaches:

1. Extending the Thread Class

You can create a thread by defining a class that extends the Thread class and overriding its
run() method.

Example:

class MyThread extends Thread {


public void run() {
// Code that defines the behavior of the thread
System.out.println("Thread is running");
}
}

public class Main {


public static void main(String[] args) {
MyThread thread = new MyThread(); // Create an instance of MyThread
thread.start(); // Start the thread
}
}

2. Implementing the Runnable Interface

Another way to define a thread is by implementing the Runnable interface and passing an
instance of the class to a Thread object.

Example:
class MyRunnable implements Runnable {
public void run() {
// Code that defines the behavior of the thread
System.out.println("Thread is running");
}
}

public class Main {


public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable); // Create a Thread object and pass
MyRunnable to it
thread.start(); // Start the thread
}
}

Getting and setting name of thread:-

In Java, you can get and set the name of a thread using the getName() and setName()
methods provided by the Thread class.

1. Setting the Thread Name

You can set the name of a thread either:

 By passing the name in the constructor of the Thread class.


 By using the setName(String name) method.

2. Getting the Thread Name

You can retrieve the name of a thread using the getName() method.

Example 1: Setting the Name Using the Constructor

class MyRunnable implements Runnable {


public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
}

public class Main {


public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable(), "MyCustomThread");
thread.start();
}
}

Output:
Thread running: MyCustomThread
Example 2: Setting and Getting the Name Using setName() and getName()

class MyRunnable implements Runnable {


public void run() {
Thread.currentThread().setName("WorkerThread");
System.out.println("Thread running: " +
Thread.currentThread().getName());
}
}

public class Main {


public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setName("InitialThread"); // Set the
thread name before it starts
thread.start();

System.out.println("Thread name before running: "


+ thread.getName());
}
}

Output:
Thread name before running: InitialThread
Thread running: WorkerThread

Key Points:

 setName(String name): Used to set the name of the thread.


 getName(): Used to retrieve the name of the thread.
 You can change the thread's name at any point, even after it has started, as
demonstrated in the second example.

Thread priorities :

In Java, thread priorities determine the relative importance of threads when they are competing for
CPU time. Each thread is assigned a priority, and the thread scheduler uses these priorities to
determine which thread should run next. However, thread scheduling behavior can vary across
different platforms and JVM implementations, so thread priority does not guarantee execution order.

Key Concepts:
1. Thread Priority Range: Java thread priorities are integers between 1 and 10:
o Thread.MIN_PRIORITY = 1 (lowest priority)
o Thread.NORM_PRIORITY = 5 (default priority)
o Thread.MAX_PRIORITY = 10 (highest priority)
2. Setting Thread Priority: You can set a thread's priority using the setPriority()
method:

Thread thread = new Thread();


thread.setPriority(Thread.MAX_PRIORITY); // Sets the thread's priority to the maximum
value (10)

3. Getting Thread Priority: You can get the current priority of a thread using the
getPriority() method:
int priority = thread.getPriority();

4. Thread Scheduling:

Threads with higher priorities are more likely to be scheduled for execution
compared to those with lower priorities.

Time-slicing: Even though a thread with a higher priority might be favored, it doesn’t
necessarily mean that a lower-priority thread will not execute. The operating system
and JVM implement time-slicing mechanisms.

Platform dependency: Thread scheduling depends on the underlying operating


system. On some systems, thread priorities might be ignored altogether.

Example:

class MyThread extends Thread {


public void run() {
System.out.println(Thread.currentThread().getName() + " with priority " +
Thread.currentThread().getPriority());
}
}

public class ThreadPriorityDemo {


public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();

t1.setPriority(Thread.MIN_PRIORITY); // t1 gets lowest priority (1)


t2.setPriority(Thread.NORM_PRIORITY); // t2 gets normal priority (5)
t3.setPriority(Thread.MAX_PRIORITY); // t3 gets highest priority (10)

t1.start();
t2.start();
t3.start();
}
}

Important Points:

 Thread priorities may not behave as expected across different JVMs and operating
systems, so it's not a good practice to rely heavily on priorities for thread
management.
 Java provides higher-level concurrency tools, such as Executors and Locks, which
are generally preferred over managing thread priorities directly.

Thread life cycle :

The method to prevent thread execution :

In Java, you can prevent thread execution in several ways depending on the scenario. Here are some
common methods:

1. Using Thread.sleep()

You can make a thread sleep for a specified amount of time, effectively preventing its execution for
that period.

try {
Thread.sleep(1000); // Pauses execution for 1 second
} catch (InterruptedException e) {
e.printStackTrace();
}
2. Using Thread.yield()

The yield() method hints to the thread scheduler that the current thread is willing to yield
its current use of a processor. It doesn't prevent execution, but it gives other threads a
chance to execute.
Thread.yield(); // Allows other threads to execute

3. Using join()

The join() method can be used to stop the execution of the current thread until another
thread finishes its execution.
Thread t1 = new Thread(() -> {
// Task for t1
});
t1.start();

try {
t1.join(); // Current thread waits until t1 finishes
} catch (InterruptedException e) {
e.printStackTrace();
}

Synchronization :
In Java, synchronization is a process used to control the access of multiple threads to shared
resources. Without synchronization, it’s possible for multiple threads to access and modify
shared data concurrently, which can lead to inconsistent or incorrect results. Java provides
several mechanisms to implement synchronization, helping ensure that only one thread can
access a resource at a time.

Key Concepts of Synchronization in Java:

1. Synchronized Block: A block of code can be synchronized by using the


synchronized keyword. This ensures that only one thread can execute the
synchronized block at a time, while others wait for the lock.
synchronized (object) {
// synchronized code
}
Example:

synchronized (this) {
// Critical section code
}

2. Synchronized Method: You can make an entire method synchronized by using the
synchronized keyword in the method declaration. The object lock is automatically
acquired when the method is called, and other threads must wait until the lock is released.

public synchronized void methodName() {

// method code

3. Static Synchronized Method: Static methods can also be synchronized, but the lock is on
the class object (Class), not on an instance of the class. This means that only one thread
can execute a static synchronized method across all instances of the class.

public static synchronized void staticMethodName() {

// method code

Example:

class Counter {

private int count = 0;


public synchronized void increment() {

count++; // Critical section

public int getCount() {

return count;

public class Main {

public static void main(String[] args) {

Counter counter = new Counter();

// Create multiple threads that try to access the shared counter

Thread t1 = new Thread(() -> {

for (int i = 0; i < 1000; i++) {

counter.increment();

});

Thread t2 = new Thread(() -> {

for (int i = 0; i < 1000; i++) {

counter.increment();

});
t1.start();

t2.start();

try {

t1.join();

t2.join();

} catch (InterruptedException e) {

e.printStackTrace();

// Since increment() is synchronized, we get the correct result

System.out.println("Final count: " + counter.getCount());

In the above example, the increment method is synchronized, so the shared resource count is
accessed by only one thread at a time. This ensures that the final value of count is correct after both
threads complete their execution.

Synchronization Best Practices:

 Synchronize only when necessary to avoid performance issues.


 Use volatile for lightweight thread visibility without needing full synchronization.
 Consider using higher-level concurrency utilities like ExecutorService and
ConcurrentHashMap for more advanced synchronization tasks.

Examples for practice :


Yield method :
package lembda;

class YieldExample extends Thread {


public YieldExample(String name) {
super(name);
}

public void run() {


for (int i = 0; i < 5; i++) {

System.out.println(Thread.currentThread().getName() + " is
running");
// Calling yield() to give a hint to the scheduler
Thread.yield();
}
}

public static void main(String[] args) {


YieldExample thread1 = new YieldExample("Thread 1");
YieldExample thread2 = new YieldExample("Thread 2");

thread1.start();
thread2.start();
}
}

Join method :

package lembda;

class JoinExample extends Thread {


public JoinExample(String name) {
super(name);
}

public void run() {


for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "
is running");
try {
Thread.sleep(1000); // Simulating some
work by sleeping for 1 second
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) throws


InterruptedException {
JoinExample thread1 = new JoinExample("Thread 1");
JoinExample thread2 = new JoinExample("Thread 2");
JoinExample thread3 = new JoinExample("Thread 3");

thread1.start();
thread1.join(); // Main thread waits for thread1 to
finish

thread2.start();
thread3.start();
thread2.join(); // Main thread waits for thread2 to
finish
thread3.join(); // Main thread waits for thread3 to
finish

System.out.println("All threads are finished, main


thread can proceed.");
}
}

Syncronized block :
package lembda;

class Counter {
private int count = 0;

// Method to increment the count using a synchronized


block
public void increment() {
// Synchronizing on the current object (this)
synchronized (this) {
count++;
System.out.println(Thread.currentThread().getName());
}
}

public int getCount() {


return count;
}
}

class SyncThread extends Thread {


private Counter counter;

public SyncThread(Counter counter) {


this.counter = counter;
}

public void run() {


for (int i = 0; i < 1000; i++) {
counter.increment(); // Each thread increments
the counter
}
}
}

public class SynchronizedBlockExample {


public static void main(String[] args) {
Counter counter = new Counter(); // Shared resource

SyncThread thread1 = new SyncThread(counter);


SyncThread thread2 = new SyncThread(counter);

thread1.start();
thread2.start();

// Wait for both threads to finish


try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

// Output the final count


System.out.println("Final count: " +
counter.getCount());
}
}
Syncronized method :
package lembda;

class Counter1 {
private int count = 0;

// Synchronized method to increment the count


public synchronized void increment() {
count++;
// line of code
}

public int getCount() {


return count;
}
}

class MyThread extends Thread {


Counter counter;

MyThread(Counter counter) {
this.counter = counter;
}

public void run() {


for (int i = 0; i < 1000; i++) {
counter.increment();
}
}
}

public class SynchronizedExample {


public static void main(String[] args) throws
InterruptedException {
Counter counter = new Counter();

MyThread t1 = new MyThread(counter);


MyThread t2 = new MyThread(counter);

t1.start();
t2.start();

t1.join(); // Wait for thread t1 to finish


t2.join(); // Wait for thread t2 to finish

// After both threads have finished, print the final


count
System.out.println("Final count: " +
counter.getCount());
}
}

Inter Thread communication :

Inter-thread communication in Java is typically done using methods like wait(), notify(),
and notifyAll() from the Object class. These methods help threads communicate by
allowing one thread to pause its execution until another thread notifies it. This is often used in
scenarios like producer-consumer problems.

Example of Inter-thread Communication

Let's consider a simple example where a producer thread generates a value, and a consumer
thread consumes that value. The consumer has to wait until the producer provides the value.

class SharedResource {
private int value;
private boolean hasValue = false;

// synchronized method for producing value


public synchronized void produce(int value) {
while (hasValue) {
try {
wait(); // wait if value is already produced
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Thread interrupted: " + e);
}
}
this.value = value;
hasValue = true;
System.out.println("Produced: " + value);
notify(); // notify the consumer that a value is produced
}
// synchronized method for consuming value
public synchronized void consume() {
while (!hasValue) {
try {
wait(); // wait until the value is produced
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Thread interrupted: " + e);
}
}
System.out.println("Consumed: " + value);
hasValue = false;
notify(); // notify the producer that the value has been consumed
}
}

class Producer implements Runnable {


private SharedResource resource;

public Producer(SharedResource resource) {


this.resource = resource;
}

@Override
public void run() {
for (int i = 1; i <= 5; i++) {
resource.produce(i);
try {
Thread.sleep(1000); // simulate time to produce
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}

class Consumer implements Runnable {


private SharedResource resource;
public Consumer(SharedResource resource) {
this.resource = resource;
}

@Override
public void run() {
for (int i = 1; i <= 5; i++) {
resource.consume();
try {
Thread.sleep(1500); // simulate time to consume
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}

public class InterThreadCommExample {


public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread producerThread = new Thread(new Producer(resource));
Thread consumerThread = new Thread(new Consumer(resource));

producerThread.start();
consumerThread.start();
}
}

Explanation:

 SharedResource: This class holds the shared value between producer and consumer
threads. It has methods to produce and consume the value.
 produce(): The producer thread calls this method. If a value has already been
produced (i.e., hasValue == true), the thread waits until the consumer consumes it.
 consume(): The consumer thread calls this method. If no value has been produced yet
(i.e., hasValue == false), the thread waits until the producer generates a value.
 wait(): This causes the current thread to wait until another thread calls notify() on
the same object.
 notify(): Wakes up a single thread that is waiting on this object's monitor.

Produced: 1
Consumed: 1
Produced: 2
Consumed: 2
Produced: 3
Consumed: 3
Produced: 4
Consumed: 4
Produced: 5
Consumed: 5

This example shows how one thread can wait for another to finish a task before proceeding,
demonstrating basic inter-thread communication in Java.

DeadLock :
A deadlock in Java occurs when two or more threads are blocked forever, waiting for each
other to release resources. This happens when multiple threads hold locks and attempt to
acquire locks held by other threads, forming a circular dependency.

Example of a Deadlock in Java

class A {

synchronized void methodA(B b) {

System.out.println("Thread 1: Locked A");

try { Thread.sleep(100); } catch (InterruptedException e) {}

System.out.println("Thread 1: Trying to lock B");

b.last(); // Thread 1 tries to call B's method

}
synchronized void last() {

System.out.println("Inside A.last()");

class B {

synchronized void methodB(A a) {

System.out.println("Thread 2: Locked B");

try { Thread.sleep(100); } catch (InterruptedException e) {}

System.out.println("Thread 2: Trying to lock A");

a.last(); // Thread 2 tries to call A's method

synchronized void last() {

System.out.println("Inside B.last()");

public class DeadlockDemo implements Runnable {

A a = new A();

B b = new B();
DeadlockDemo() {

Thread t = new Thread(this);

t.start();

a.methodA(b); // Main thread gets lock on A, tries to get lock on B

public void run() {

b.methodB(a); // Child thread gets lock on B, tries to get lock on A

public static void main(String[] args) {

new DeadlockDemo();

package lembda;

class DeadlockExample {
// Two resource objects
private final Object resource1 = new Object();
private final Object resource2 = new Object();

// Thread 1 tries to lock resource1, then


resource2
public void method1() {
synchronized (resource1) {
System.out.println("Thread 1: Locked
resource 1");
try {
// Simulating some work with sleep
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (resource2) {
System.out.println("Thread 1: Locked
resource 2");
}
}
}

// Thread 2 tries to lock resource2, then


resource1
public void method2() {
synchronized (resource2) {
System.out.println("Thread 2: Locked
resource 2");

try {
// Simulating some work with sleep
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (resource1) {
System.out.println("Thread 2: Locked
resource 1");
}
}
}

public static void main(String[] args) {


DeadlockExample deadlock = new
DeadlockExample();

// Thread 1 tries to execute method1


Thread t1 = new Thread(() ->
deadlock.method1());
// Thread 2 tries to execute method2
Thread t2 = new Thread(() ->
deadlock.method2());

t1.start();
t2.start();
}
}

Explanation:

 Thread 1 (main thread) holds the lock on object A and tries to get the lock on object
B.
 Thread 2 (child thread) holds the lock on object B and tries to get the lock on object
A.
 Since neither thread can acquire the lock held by the other, both are stuck, and this
causes a deadlock.

How to Avoid Deadlock

1. Avoid nested locks: Try to avoid locking multiple resources at the same time.
2. Use a timeout for locks: This can allow the program to continue if a thread can’t
acquire a lock within a specified time.
3. Lock ordering: Always acquire locks in a consistent order.

This is a classic case of deadlock in multi-threaded programming.

Demon Thread :

A daemon thread in Java is a type of thread that runs in the background and performs tasks
like garbage collection, memory management, or other background tasks. Unlike regular
threads, daemon threads are terminated by the JVM when all user (non-daemon) threads have
finished execution. Daemon threads are not meant to keep the program running.

You can mark a thread as a daemon thread using the setDaemon(true) method before
starting the thread. Here's an example:

Example of Daemon Thread in Java:


class DaemonThreadExample extends Thread {

public void run() {

if (Thread.currentThread().isDaemon()) {

System.out.println(Thread.currentThread().getName() + " is a
daemon thread.");

} else {

System.out.println(Thread.currentThread().getName() + " is a user


thread.");

// Simulating background work

for (int i = 0; i < 5; i++) {

System.out.println(Thread.currentThread().getName() + "
executing: " + i);

try {

Thread.sleep(500); // Sleep for 500 milliseconds

} catch (InterruptedException e) {

e.printStackTrace();

public static void main(String[] args) {

DaemonThreadExample t1 = new DaemonThreadExample(); // User


thread

DaemonThreadExample t2 = new DaemonThreadExample(); //


Daemon thread
t1.setDaemon(false); // Mark t1 as a user thread (optional, as it's
default)

t2.setDaemon(true); // Mark t2 as a daemon thread

t1.start();

t2.start();

Output:

Thread-0 is a user thread.

Thread-1 is a daemon thread.

Thread-0 executing: 0

Thread-1 executing: 0

Thread-0 executing: 1

Thread-1 executing: 1

Thread-0 executing: 2

Thread-1 executing: 2

Thread-0 executing: 3

Thread-1 executing: 3

Thread-0 executing: 4

Explanation:
 Thread-0 (a user thread) continues its execution even after the daemon thread
Thread-1 starts.
 If the Thread-0 completes its execution, the JVM will terminate all daemon threads,
even if they haven't finished their work. So, in this case, the daemon thread Thread-1
might not print all its iterations, depending on the timing.

Key Points:

 Daemon threads run in the background and are terminated by the JVM once all user
threads are finished.
 Use the setDaemon(true) method to make a thread a daemon.
 Daemon threads are useful for tasks like garbage collection, but they should not be
relied upon for critical tasks, as they may not complete if the JVM exits.

Multithreading enhancement

Lock :
In Java multithreading, a reentrant lock is a type of lock that allows a thread to enter a
synchronized block of code that is already locked by the same thread. It prevents the situation
where a thread would block itself because it already owns the lock.

The ReentrantLock class from the java.util.concurrent.locks package provides this


functionality. It offers more flexibility and control over thread synchronization than the
synchronized keyword. One of the key benefits of ReentrantLock is that it allows lock
acquisition in a non-blocking way, with features such as try-lock and timeout.
Key features of ReentrantLock:

1. Lock Acquisition: A thread can acquire the lock multiple times (reentrant). It must
release the lock the same number of times to fully unlock it.
2. Fairness: You can create a fair lock by passing true to the ReentrantLock
constructor. In a fair lock, threads acquire locks in the order they requested them,
which reduces the chance of starvation.
3. Interruptible Lock Acquisition: A thread can interrupt another thread waiting to
acquire the lock.
4. Try-Lock: Instead of waiting indefinitely, a thread can attempt to acquire the lock
and return immediately if it’s unavailable.
5. Condition Variables: ReentrantLock allows you to associate multiple Condition
objects with it, which can be used for more sophisticated wait/notify patterns.

Example usage:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {


private final ReentrantLock lock = new ReentrantLock();

public void performTask() {


lock.lock(); // Acquiring the lock
try {
System.out.println(Thread.currentThread().getName() + " has
acquired the lock");
// Critical section (code that needs synchronization)
} finally {
lock.unlock(); // Releasing the lock
System.out.println(Thread.currentThread().getName() + " has
released the lock");
}
}

public static void main(String[] args) {


ReentrantLockExample example = new
ReentrantLockExample();

Runnable task = () -> {


for (int i = 0; i < 3; i++) {
example.performTask();
}
};

Thread t1 = new Thread(task, "Thread-1");


Thread t2 = new Thread(task, "Thread-2");

t1.start();
t2.start();
}
}

Key methods in ReentrantLock:

 lock(): Acquires the lock.


 unlock(): Releases the lock.
 tryLock(): Attempts to acquire the lock without waiting.
 lockInterruptibly(): Acquires the lock unless the current thread is interrupted.
 newCondition(): Returns a new Condition object bound to this Lock instance.

The ReentrantLock class offers more control over synchronization, making it useful in
situations where the built-in synchronized keyword may be too limited.

Thread pool :

A Thread Pool in Java is a mechanism for managing multiple threads efficiently by reusing a
fixed number of threads to execute tasks. Instead of creating a new thread for every task, a
thread pool reuses existing threads to handle multiple tasks, which can improve performance
and reduce overhead.

Java provides an ExecutorService interface that facilitates thread pool management. The
most common implementation is through the ThreadPoolExecutor class or the Executors
utility class.

Here’s an example of using a fixed thread pool in Java:

Example: Java Thread Pool using Executors and ExecutorService

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

class Task implements Runnable {

private String taskName;

public Task(String name) {

this.taskName = name;

@Override
public void run() {

System.out.println(Thread.currentThread().getName() + " is
executing task: " + taskName);

try {

Thread.sleep(1000); // Simulate some work

} catch (InterruptedException e) {

e.printStackTrace();

System.out.println(taskName + " completed.");

public class ThreadPoolExample {

public static void main(String[] args) {

// Create a thread pool with 3 threads

ExecutorService threadPool = Executors.newFixedThreadPool(3);

// Submit tasks for execution

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

Task task = new Task("Task " + i);

threadPool.execute(task);

// Shutdown the pool after all tasks are executed


threadPool.shutdown();

Explanation:

1. ExecutorService: This is an interface that allows us to manage a pool of threads and


submit tasks for execution.
2. Executors.newFixedThreadPool(3): This creates a thread pool with 3 threads. You
can adjust the number of threads based on the use case.
3. Task class: Implements Runnable interface to define the work each thread will
execute. The run() method contains the task's logic.
4. threadPool.execute(task): This submits tasks to the thread pool. The pool will assign
tasks to available threads, and if all threads are busy, the tasks will wait in a queue.
5. threadPool.shutdown(): This method tells the pool to stop accepting new tasks and
finish all the existing tasks.

pool-1-thread-1 is executing task: Task 1

pool-1-thread-2 is executing task: Task 2

pool-1-thread-3 is executing task: Task 3

Task 1 completed.

pool-1-thread-1 is executing task: Task 4

Task 2 completed.

pool-1-thread-2 is executing task: Task 5

Task 3 completed.

Task 4 completed.

Task 5 completed.

This example shows how tasks are assigned to the available threads in the pool. Once a
thread completes its current task, it moves on to the next one in the queue.
You can use other thread pool types like:

 newCachedThreadPool(): Creates a thread pool that creates new threads as needed,


but reuses previously constructed threads when available.
 newSingleThreadExecutor(): Creates a single thread to execute tasks sequentially.

You might also like