0% found this document useful (0 votes)
2 views35 pages

Multi Threading

The document provides an overview of threads and multithreading in Java, explaining concepts such as race conditions, the volatile keyword, and the ExecutorService framework for managing threads efficiently. It details various methods for creating threads, the differences between Executor, ExecutorService, and Executors, and highlights the importance of thread pools for performance and resource management. Additionally, it covers key thread methods and their purposes, emphasizing the abstraction provided by ExecutorService to simplify concurrency management.

Uploaded by

rohansirsa052
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)
2 views35 pages

Multi Threading

The document provides an overview of threads and multithreading in Java, explaining concepts such as race conditions, the volatile keyword, and the ExecutorService framework for managing threads efficiently. It details various methods for creating threads, the differences between Executor, ExecutorService, and Executors, and highlights the importance of thread pools for performance and resource management. Additionally, it covers key thread methods and their purposes, emphasizing the abstraction provided by ExecutorService to simplify concurrency management.

Uploaded by

rohansirsa052
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

🔹 What is a Thread?

The basic unit of execution that run independently within a program but
share the same memory space.

🔹 Why Multithreading?

 Perform multiple tasks simultaneously

 Improve application performance

 Efficient use of CPU resources

 Used heavily in web servers, Spring Boot apps, microservices,


and batch processing

🔹 Ways to Create Threads in Java

1️⃣ By Extending Thread Class

2️⃣ By Implementing Runnable Interface (Recommended)


🔴 What is a Race Condition in Java?

A race condition occurs when two or more threads access and


modify a shared resource at the same time, and the final result
depends on the order of execution of those threads.

=> Because thread scheduling is unpredictable, the output becomes


inconsistent or incorrect.

🔹 Simple Definition

Race Condition = Multiple threads + Shared data + No proper


synchronization

🔴 Race Condition Example (Problem)

❌ Code WITH Race Condition


❓ Expected Output

Final Count: 2000

Actual Output (varies)

Final Count: 1634

Final Count: 1789

Final Count: 1991


✔ No overlap
✔ No lost updates

🔹 volatile Keyword in Java

The volatile keyword in Java is used to ensure visibility of changes to


a variable across multiple threads.

It guarantees that every read of the variable gets the latest value
from main memory, not a cached copy from a thread’s local CPU cache.

volatile => visibility


Synchronized

 No overlap
 No update loss
 Example program counter variable

Volatile

 Visibility across multiple thread


 Example program SharedData, ReaderThread and WriterThread

Concurrency [ Multiple Robots Independently performing war on


diff countries with same time and CPU, Memory]

Executing multiple thread

 independently
 making progress within the same time period
 Same CPU and memory resource efficiently

Asynchronously [ 1 Robot is Mainly preparing for Bath and others


are helping independently]

 A task runs independently


 without blocking the main flow
 while waiting for a result.

ExecutorService in Java
ExecutorService is a Java concurrency framework (from
[Link]) used to execute and manage threads efficiently,
without manually creating and controlling threads.

❌ Problems with using Thread directly (Why ExecutorService is needed)

Creating many threads is expensive

Each thread needs memory and CPU time. Creating threads repeatedly
increases overhead and slows down the application.

No thread reuse

Once a thread completes its task, it is destroyed. For every new task, a
new thread must be created again.

Hard to control number of threads

There is no built-in way to limit how many threads are created, which can
overload CPU and system resources.

No proper lifecycle management

You must manually start, stop, and manage threads, which makes the
code complex and error-prone.

Difficult to handle failures

If a thread throws an exception, it is hard to detect, track, or restart the


task automatically.

Risk of OutOfMemoryError

Creating too many threads can consume all available memory, causing
the application to crash.

Why ExecutorService is needed

ExecutorService solves these problems by:

 Managing a pool of threads

 Reusing threads instead of creating new ones

 Limiting the number of concurrent threads

 Managing task scheduling

 Providing clean shutdown

 Improving performance and scalability

You submit tasks, ExecutorService handles threads.


ExecutorService is an INTERFACE, not a class.

What is Executors?

Executors is a FACTORY CLASS It provides static factory methods to


create different types of ExecutorService objects.
What is Executor?

Executor is an interface in Java used to execute a task (Runnable)


using the execute() method.

Java pgm use of executor service refers notes

import [Link];
import [Link];
import [Link];

class Mytask implements Runnable


{
@Override
public void run()
{
[Link](" executing task");
}
}
public class ExecutorAllInOneExample {
public static void main(String[] args) {

ExecutorService executorService =
[Link](2);
Executor executor = executorService;
[Link](new Mytask());
[Link](new Mytask());
[Link](new Mytask());
[Link]();
}
}

Even though multiple tasks are submitted, a fixed thread pool creates only
a fixed number of threads.
Extra tasks are placed in a queue and executed by the same threads once
they become free.

Thread pool size = number of threads, not number of tasks

How do you create an ExecutorService?

You can create an ExecutorService using the factory methods provided by


the Executors class:

1. [Link](int nThreads) nftp


 creates a fixed-size thread pool.
 Only nThreads tasks can run in parallel at any time.
 Threads are reused, so new threads are not created for every
task.
 This gives better performance and controlled resource
usage.

Real-world scenario example: Order Processing System

Imagine an e-commerce application where only 3 orders can be


processed at a time by workers.
OUTPUT

**********************

You submitted more tasks than threads, so extra tasks were queued.

The order of execution is not sequential because threads run


concurrently.
When does pool-2 get created?

Whenever you create another ExecutorService in the same JVM.


ExecutorService executor1 = [Link](2);

ExecutorService executor2 = [Link](2);

First ExecutorService created → pool-1

Second ExecutorService created → pool-2

Each pool maintains its own threads

Pool numbers increase globally inside JVM

2. [Link]() nctp
 creates threads dynamically as tasks arrive.
 creates a thread pool that can expand as needed.
If no idle thread is available, it creates a new thread
immediately.
Idle threads are reused for new tasks instead of creating
new ones.
If a thread remains idle for 60 seconds, it is removed to
free resources.
There is no fixed upper limit on the number of threads,
so it can grow quickly.
This is best for short-lived, lightweight tasks
newCachedThreadPool() is used when tasks are short-lived
and the workload is unpredictable, allowing the pool to
grow and shrink automatically.
3. [Link]() nste
 creates a single-threaded executor.
 All submitted tasks are executed one after another
(sequentially).
 Tasks are placed in a queue and processed in the
order they are submitted.
 It guarantees no parallel execution, which avoids
concurrency issues.
The single thread is reused for all tasks.
If the thread fails, the executor creates a new one
automatically.
4. [Link](int corePoolSize)
 creates a pool that supports scheduled tasks.
 creates a scheduled executor with a fixed number of
threads used to run tasks after a delay or periodically.
 It is commonly used for background jobs, polling, and
maintenance tasks.
What is Executors?

Executors – it is the Factory class provides different methods to create


object Executors and ExecutorService

Package: [Link]

Example:

ExecutorService created using Executors


ExecutorService executorService = Executors. newSingleThreadExecutor
();

Excutor created using Executors

Executor executor = [Link]();

What is Executor?

Executor is an interface in Java used to execute a task without


manually creating and starting a thread.
Executor executor = [Link]();

[Link](() -> [Link]("Log: User logged in"));


[Link](() -> [Link]("Log: User placed order"));
[Link](() -> [Link]("Log: User logged out"));

What is ExecutorService?

ExecutorService is Java concurrency framework used to manage and


execute threads efficiently, without manually creating and controlling
threads.

ExecutorService executorService = [Link](2);

[Link](() -> [Link]("Log: User logged in"));


[Link](() -> [Link]("Log: User placed
order"));
[Link](() -> [Link]("Log: User logged out"));

Executor

 Interface for executing tasks

ExecutorService

 Subinterface of Executor

 Supports lifecycle management

Executors

 Factory class to create ExecutorService & Executor


Difference between Executor, ExecutorService and Executors

Difference Between execute() and submit()

execute()

Defined in Executor interface

Used to execute Runnable task

Does NOT return any result

Cannot track task status

If exception occurs → it is thrown directly


ExecutorService executor = [Link]();

[Link](new Runnable() {
public void run() {
[Link]("Task executed");
}
});
Just runs the task. No return value.

submit()
Defined in ExecutorService interface

Can execute:

o Runnable

o Callable

Returns Future object

Can get result using [Link]()

Can check task status

Exceptions are captured inside Future

Future<Integer> future = [Link](new Callable<Integer>() {


public Integer call() {
return 50;
}
});

[Link]("Result: " + [Link]());


[Link]();

Returns result, Can monitor task


ExecutorService executor = [Link]();

//Using execute() method

[Link](new Runnable() {
@Override
public void run() {
[Link]("execute() method running");
}
});

// Using submit() method

Future<Integer> future = [Link](new Callable<Integer>() {


@Override
public Integer call() {
return 100;
}
});

[Link]("submit() returned value: " + [Link]());

✅ get()

[Link]() is used to retrieve the result of a task submitted to an ExecutorService. It


waits indefinitely until the task completes. If the task finishes successfully, it returns
the result. If the task throws an exception, the exception is wrapped inside an
ExecutionException and thrown when calling get().

This method blocks the calling thread until the computation is done. If the task takes
5 seconds, get() will wait 5 seconds. If the task never completes, it will wait forever.
Because of this, it is simple to use but risky in real-world systems if not handled carefully.

✅ get(long timeout, TimeUnit unit)

[Link](timeout, unit) is used to retrieve the result but with a time limit. It waits only
for the specified duration. If the task completes within that time, it returns the result
normally. If the task does not finish within the given time, it throws a TimeoutException.

This method is safer for production systems because it prevents the application from
waiting forever. It still blocks the thread, but only for the specified duration. It is
commonly used in real-world applications where response time control is important, such
as APIs and distributed systems.
Thread Pool

What is a Thread Pool?

A thread pool is a collection of pre-created worker threads that execute


submitted tasks.

Instead of creating a new thread for every task,


the pool reuses existing threads.

Complete story how threads are handled:

Think of a thread pool as a small team of workers managed by a


supervisor. In Java, this system is controlled using the ExecutorService
interface (an interface used to manage and control execution of multiple
tasks and thread lifecycle), and the actual working is handled by the
ThreadPoolExecutor class (a concrete class that implements
ExecutorService and manages threads internally). When you create a pool
using the Executors class (a factory/utility class used to create thread
pools easily), for example newFixedThreadPool, a fixed number of worker
threads are created and kept ready. These threads initially stay idle,
waiting for tasks.

When you submit a task using execute() (method to run a task without
returning result) or submit() (method to run a task and return a result
using Future), the task is given to the pool. The pool first checks if any
thread is free; if yes, that thread immediately starts executing the task. If
all threads are busy, the task is placed into a BlockingQueue (a queue
that holds tasks and allows threads to safely take tasks one by one),
where it waits in line.

As soon as a thread finishes its current work, it goes back to the queue
and picks the next task. In this way, threads are reused again and again
instead of being created every time, which improves performance and
saves memory. If too many tasks are submitted and the queue becomes
full, the pool uses a Rejection Policy (a rule that decides what to do
when no more tasks can be accepted, like throwing an exception).
Internally, the thread pool manages core threads, maximum threads, and
task queues efficiently without developer intervention. Finally, when all
tasks are done, calling shutdown() (method to stop accepting new tasks
and finish existing ones) ensures a clean and proper shutdown. This entire
mechanism makes thread pools efficient, scalable, and ideal for real-world
applications like servers and APIs.

 Executor → basic task execution (execute only)

 ExecutorService → adds lifecycle + result handling

 AbstractExecutorService → provides common implementation

 ThreadPoolExecutor → actual thread pool working

 ScheduledThreadPoolExecutor → supports delayed/scheduled


tasks

 ForkJoinPool → used for parallel processing (divide & conquer)

ForkJoinPool is class =>This implementation rejects submitted tasks


(that is, by throwing RejectedExecutionException) only when the
pool is shut down or internal resources have been exhausted

Executor → ExecutorService → ThreadPoolExecutor (real worker)


🔹 Basic Explanation

In Java, methods like sleep(), join(), yield(), and interrupt() belong to the
Thread class and are used for low-level thread control. These methods
help in pausing, waiting, or managing individual threads manually.
However, when using ExecutorService, we do not directly use these
methods as part of its API. ExecutorService works at a higher level of
abstraction where we deal with tasks instead of threads. Instead of
controlling threads manually, we submit tasks using execute() or submit().
The framework internally manages thread creation, execution, and reuse.
Methods like join() are replaced by [Link]() for waiting. The developer
does not directly interact with thread lifecycle. This makes code cleaner
and less error-prone. So, these Thread methods are not commonly used in
ExecutorService-based design.

🔹 Why

ExecutorService is designed to hide low-level thread management


complexity.
It provides better control using task-based execution instead of thread-
based handling.
Manual thread control using methods like join() or sleep() can lead to
errors.
Using high-level APIs improves scalability and maintainability.
Hence, these methods are avoided in modern concurrent programming.

🔹 How

Instead of join(), we use [Link]() to wait for task completion.


Instead of manual thread creation, we use submit() or execute().
Thread lifecycle is managed internally using thread pools.
Tasks are queued and executed automatically without developer
intervention.
This abstraction makes concurrency simpler and more efficient.

Final Line

ExecutorService avoids low-level Thread methods by providing high-level


task execution APIs, improving performance and simplifying concurrency
management.
Important Thread Methods – Used in Thread class

1️⃣ sleep() [STOP – Continue]

Imagine you set an alarm for 10 minutes ⏰

You stop all activity and rest for 10 minutes → sleep()

After time is over, you automatically resume work

[sleep() is like taking a timed pause and then resuming execution


after the delay]

Purpose:
Pause the current thread for a specified time

Example:

class SleepExample {
public static void main(String[] args) throws Exception {
[Link]("Start");
[Link](2000); // pause 2 seconds(in milliseconds)
[Link]("End");
}
}

2️⃣ join()

[ Wait for another thread finishes task]

[You cannot proceed until the other task finishes]

Purpose:
Make one thread wait until another thread finishes

Example:
class JoinExample extends Thread {

public void run() {

[Link]("Child thread running");

public static void main(String[] args) throws Exception {

JoinExample t1 = new JoinExample();

[Link]();

[Link](); // main waits for t1

[Link]("Main thread continues");

3️⃣ start()

Purpose:
Starts a new thread and calls run()

Example:

class StartExample extends Thread {


public void run() {
[Link]("Thread started");
}

public static void main(String[] args) {


StartExample t = new StartExample();
[Link](); // starts new thread
}
}

4️⃣ run()

Purpose:
Contains the task logic executed by the thread

Example:

class RunExample extends Thread {


public void run() {
[Link]("Running task");
}
}

5️⃣ yield()

Purpose:

[ You are giving a chance, not forcing a switch]

Pause current thread and give chance to other threads

Example:

class YieldExample extends Thread {


public void run() {
for (int i = 0; i < 3; i++) {
[Link]([Link]().getName());
[Link]();
}
}

public static void main(String[] args) {


new YieldExample().start();
new YieldExample().start();
}
}

6️⃣ interrupt()

Purpose:
Interrupt a sleeping or waiting thread

Real-world (Application) example

👉 Imagine a file download in an application 📥

 User starts downloading a large file → thread is running

 Download is taking time (thread may be waiting/sleeping)

 User clicks Cancel Download ❌

 Application interrupts the thread → interrupt()

✔ Download stops immediately


✔ Thread exits from waiting/sleep state
Example:

class InterruptExample extends Thread {


public void run() {
try {
[Link](5000);
} catch (Exception e) {
[Link]("Thread interrupted");
}
}

public static void main(String[] args) {


InterruptExample t = new InterruptExample();
[Link]();
[Link]();
}
}

7️⃣ isAlive()

Imagine a report generation process in an application 📊

 User starts generating a report → thread starts

 Application checks: is report still generating? → isAlive()

 If true → show “Processing…” to user

 If false → show “Download Report”

✔ Helps track if a task is still running


✔ Used for status checking in UI/backend

🔹 Key idea

Used to check whether a task is still in progress or completed

🔹 One-line answer

isAlive() is used to check if a thread is still running, like verifying


if a report generation task is in progress.

Purpose:
Check if thread is still running
Example:

class AliveExample extends Thread {


public void run() {
[Link]("Thread running");
}

public static void main(String[] args) {


AliveExample t = new AliveExample();
[Link]([Link]()); // false
[Link]();
[Link]([Link]()); // true
}
}
Real-world use cases - 1 example of threads using executorservice

 HL7FileProcessingService → Handles incoming HL7 files and


manages overall processing
 HL7ParserService → Parses HL7 file into structured data
 ValidationService → Validates data (format, mandatory fields,
business rules)
 TransformationService → Converts HL7 data into internal model
 PersistenceService → Saves processed data into database
 ReportService → Generates success/failure reports
 LoggingService → Logs errors and processing details
 ExecutorService → Executes file processing tasks in parallel using
thread pool
🔄 Simple Lifecycle Flow

🔹 Normal Thread

New → Runnable → Running → (Waiting/Blocked) → Terminated

🔹 ExecutorService

Create Pool → Submit Task → Task Queue → Execute Task → Reuse Thread
→ Shutdown

🧠 Key Difference

 Thread → You manage thread lifecycle manually

 ExecutorService → Framework manages everything (threads +


tasks)

🎯 Interview One-Liner

In normal threads, lifecycle is manually managed from creation to


termination, whereas ExecutorService manages thread lifecycle internally
using a pool and executes tasks efficiently with reuse and better
scalability.

Runnable, Callable, Future – ALL ARE INTERFACES and from


[Link].*;

Commonly:
 Java Concurrency APIs

 Java Concurrency Constructs

 Concurrency Interfaces

Interviewers:
 Multithreading Interfaces

 Task Abstractions

 Asynchronous Programming Constructs

 Executor Framework Components

 Parallel Execution Utilities

 Threading Utilities

 Async Task Handling Mechanisms

Runnable

Definition

Runnable is an interface used to create a task that can run in a separate


thread.
It does NOT return any value and cannot throw checked
exceptions.

public class RunnableExample {


public static void main(String[] args) {
Runnable runnable = () -> [Link]("Running task");
ExecutorService executorService =
[Link](1);
[Link](runnable);
[Link]();
}
}
Callable

Definition

Callable is an interface used to create a task that runs in a thread and


returns a result.
It CAN return a value and can throw exceptions.

public class CallableExample {


public static void main(String[] args) throws ExecutionException,
InterruptedException {
ExecutorService executorService =
[Link](1);
Callable<Integer> callable = () -> {
return 10*10;
};
Future<Integer> future= [Link](callable);
[Link]([Link]());
[Link]();
}
}

Future

Definition

Future is used to get the result of a Callable task later (asynchronous


result).

Think: “I’ll give you result later, wait or check.”

import [Link].*;

public class Main {

public static void main(String[] args) throws Exception {

ExecutorService executor = [Link]();

// Callable task
Callable<Integer> task = () -> {

return 10 + 20;

};

// Submit task and get Future

Future<Integer> future = [Link](task);

// Get result

[Link]("Result: " + [Link]());

[Link](); } }

Yes ✅Callable is generic, so it can return any type.

Runnable and Callable are functional interfaces because each has a single
abstract method.
Future is a normal interface since it contains multiple abstract methods.

What type of exception does call() throw?

It throws (checked exception)

What Exception thrown by FUTURE

 ExecutionException

 InterruptedException

Common Checked Exceptions

Checked exceptions are compile-time exceptions that must be


handled using try-catch or declared using throws

 IOException

 SQLException

 FileNotFoundException

 ClassNotFoundException

 InterruptedException
What is a Functional Interface?

A functional interface is an interface that contains only one abstract


method.

Example:

@FunctionalInterface
interface MyInterface {
void execute();
}

Java 8 introduced Predicate, Function, Consumer, and Supplier.


They were NOT available in earlier Java versions.

📌 Before Java 8

 Java already had interfaces, but:

o No concept of functional interfaces (officially)

o No lambda expressions

o Developers had to use anonymous classes

1. Predicate<T>

Type of Interface:
Takes input, returns boolean

Used in:

 Filtering data (like even numbers, valid users)

 Condition checking

Simple Real-world Example:


Check if a person is eligible to vote
Predicate<Integer> isEligible = age -> age >= 18;
[Link]([Link](20)); // true

Methods are like -> test(), and(), negate(), or(), isEqual(), not() – filtering
opns;

2. Function<T, R>

Type of Interface:
Takes input, returns output

Used in:

 Data transformation

 Mapping values

Simple Real-world Example:


Convert salary to yearly salary

Function<Integer, Integer> yearlySalary = monthly -> monthly * 12;


[Link]([Link](5000)); // 60000

Methods are like ->apply(), compose(), andThen(), identity() – map or


manipulate;

3. Consumer<T>

Type of Interface:
Takes input, returns nothing

Used in:

 Printing/logging

 Saving data

Simple Real-world Example:


Print user name

Consumer<String> printName = name -> [Link](name);


[Link]("Kishore");

Methods like -> accept() - logging


4. Supplier<T>

Type of Interface:
No input, returns output

Used in:

 Generating values

 Providing default data

Simple Real-world Example:


Generate OTP

Supplier<Integer> otp = () -> 123456;


[Link]([Link]());

Methods are like -> get() -for default values;

You might also like