🔹 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;