Java Multithreading
Complete Study Notes
From Basics to Interview Preparation
Topics Covered
✅ Thread Basics & Lifecycle
✅ Ways to Create Threads
✅ Thread Methods (sleep, join, yield, priority)
✅ Synchronization & Locks
✅ ReentrantLock & Fairness Policy
✅ Deadlock – Causes & Prevention
✅ wait(), notify(), notifyAll()
✅ ExecutorService & Thread Pools
✅ Callable & Future
✅ ThreadLocal
✅ ScheduledExecutorService
✅ @Async in Spring Boot
✅ Interview Questions & Answers
1. What is a Thread & Multithreading?
1.1 What is a Thread?
A thread is the smallest unit of execution in a Java program. When you want to perform independent
tasks simultaneously, threads allow you to do so, improving your application's performance.
1.2 What is the Main Thread?
The main thread is the first thread created by the JVM when a Java program starts. It executes the
main() method.
Question Answer
Who creates the main thread? The JVM creates it automatically when the program
starts.
Default name? "main"
Default priority? 5 (NORM_PRIORITY)
Is it a daemon thread? No, it is a user thread by default.
Can we rename it? Yes, using
[Link]().setName("NewName")
Can we make it daemon? No, we cannot convert the main thread to a daemon.
What happens when it finishes? If no other user threads are running, the JVM
terminates.
1.3 What is Multithreading?
Multithreading is the ability of a Java program to execute multiple threads simultaneously. It is used
when you have multiple independent tasks that can run in parallel to improve overall performance.
📝 Example: Sending emails to Indian, US, China, and UK customers simultaneously instead of one by one.
1.4 Thread Lifecycle (States)
State Description Triggered By
Born (New) Thread object is created but not started yet. new Thread()
Ready (Runnable) Thread is scheduled with the Thread start()
Scheduler.
Running Thread Scheduler picks the thread and Thread Scheduler
executes it.
Waiting / Blocked Thread pauses (e.g., waiting for a lock or wait(), sleep(), join()
sleeping).
Dead (Terminated) Thread has finished its execution. run() completes
2. Ways to Create a Thread
Java provides multiple ways to create and run threads:
Method How Return Value Checked Exception
Extend Thread class MyThread extends void No
Thread
Implement Runnable class MyThread implements void No
Runnable
Implement Callable class MyThread implements T (any type) Yes
Callable<T>
Lambda (Java 8+) () -> { ... } passed to Thread or void / T Depends
Executor
@Async (Spring Boot) @Async annotation on a void / Depends
method Future<T>
2.1 Extending Thread Class
class MyThread extends Thread {
private String message;
public MyThread(String message) { [Link] = message; }
@Override
public void run() {
[Link]([Link]().getName() + ": " + message);
}
}
public class Test {
public static void main(String[] args) {
Thread mt1 = new MyThread("email to Indian customer");
Thread mt2 = new MyThread("email to US customer");
[Link](); // start() creates a new thread
[Link]();
}
}
⚠️ Extending Thread class means you cannot extend any other class — you lose the benefit of multiple
inheritance.
2.2 Implementing Runnable Interface (Preferred)
class MyThread implements Runnable {
public void run() {
for (int i = 1; i <= 3; i++) {
[Link]("Thread running: " + i);
}
}
}
public class RunnableExample {
public static void main(String[] args) {
MyThread obj = new MyThread(); // Runnable object
Thread t = new Thread(obj); // Wrap in Thread
[Link](); // Start new thread
}
}
📝 Runnable is preferred over Thread because you can still implement other interfaces (multiple inheritance
benefit).
2.3 run() vs start() — Important Difference
Method Where Defined What it Does
run() Runnable interface / Contains the logic to execute. Calling run() directly
Thread class does NOT create a new thread — it runs on the current
(main) thread.
start() Thread class Creates a NEW thread and registers it with the Thread
Scheduler. The scheduler calls run() automatically.
⚠️ If you call run() instead of start(), no new thread is created. The code runs on the calling thread!
2.4 Implementing Callable Interface
Use Callable when your thread needs to return a result or throw a checked exception.
class MyTask implements Callable<String> {
@Override
public String call() throws Exception {
[Link](1000);
return "Result from thread: " + [Link]().getName();
}
}
public class CallableExample {
public static void main(String[] args) throws Exception {
Callable<String> task = new MyTask();
ExecutorService executor = [Link](2);
Future<String> future = [Link](task);
[Link]([Link](5, [Link])); // waits for result
[Link]();
}
}
Feature Runnable Callable
Method run() call()
Return type void Generic <T>
Checked exceptions Cannot throw Can throw
Result retrieval Not possible Via Future<T>
Use case Fire and forget tasks Tasks that return a result
3. Important Thread Methods
3.1 sleep()
Pauses the current thread for a specified time (in milliseconds). The thread does NOT release any
locks it holds.
[Link](2000); // Pauses current thread for 2 seconds
📝 sleep() is a static method of Thread class. It always pauses the currently executing thread.
3.2 join()
Makes the current thread wait until the specified thread finishes. Use this when one task must complete
before another begins.
Thread t1 = new MyThread();
[Link]();
[Link](); // Main thread waits here until t1 finishes
[Link]("t1 has finished");
3.3 yield()
A hint to the JVM that the current thread is willing to give up the CPU for other threads of the same
priority. There is NO guarantee the JVM will act on this hint.
[Link](); // Hint: give chance to threads of same priority
3.4 setPriority() / getPriority()
Thread priority is a hint to the Thread Scheduler about relative importance. Priorities range from 1
(MIN) to 10 (MAX), with 5 (NORM) as default.
Thread t1 = new MyThread();
[Link](Thread.MIN_PRIORITY); // 1
[Link](Thread.NORM_PRIORITY); // 5 (default)
[Link](Thread.MAX_PRIORITY); // 10
int p = [Link]();
⚠️ Priority is a hint — the OS/JVM scheduler may not always follow it. Never rely on priority for critical logic.
3.5 Summary of Thread Methods
Method Defined In Purpose Releases Lock?
start() Thread Creates and starts a new thread —
run() Runnable/Thread Thread task logic —
sleep(ms) Thread Pause for given time No
join() Thread Wait for another thread to finish No
yield() Thread Hint to give up CPU No
setPriority(n) Thread Set thread scheduling priority —
wait() Object Pause and release lock Yes
notify() Object Wake one waiting thread No
notifyAll() Object Wake all waiting threads No
4. Synchronization
4.1 Why Synchronization?
When multiple threads access shared data simultaneously, they can overwrite each other's changes,
causing data inconsistency. Synchronization ensures only one thread accesses critical code at a time.
4.2 synchronized Keyword
The synchronized keyword can be applied at method level or block level.
Synchronized Method:
public synchronized void printData(String name) {
[Link](name + " - line 1");
[Link](name + " - line 2");
}
Synchronized Block (Preferred):
public void printData(String name) {
if ([Link]() < 3) {
[Link]("Invalid name");
return;
}
synchronized (this) { // Only sync the critical part
[Link](name + " - line 1");
[Link](name + " - line 2");
}
}
📝 Synchronized block is preferred over synchronized method because you only lock the critical section, not
the entire method. This improves performance.
4.3 Object Lock vs Class Lock
Lock Type When Used Scope Multiple Threads?
Object Level Lock Non-static synchronized One object Multiple threads can run
method/block instance simultaneously on
different objects
Class Level Lock Static synchronized The entire class Only one thread can run
method/block (all instances) at a time across ALL
objects
4.4 Problems with synchronized
• Performance issue: Only one thread can access the synchronized code at a time.
• Deadlock risk: Thread A holds lock1 and waits for lock2; Thread B holds lock2 and waits for lock1.
• Starvation: A thread may wait indefinitely if other threads keep acquiring the lock first.
• No fairness: synchronized does NOT guarantee which waiting thread gets the lock next.
4.5 ReentrantLock — Solution to synchronized Problems
ReentrantLock is a class in [Link] that provides more control than synchronized.
"Reentrant" means: the same thread can acquire the lock multiple times without deadlocking itself —
but must unlock the same number of times.
Method Behavior Returns Waits?
lock() Acquires lock; waits indefinitely if — Yes, forever
not available.
unlock() Releases the acquired lock. — —
tryLock() Tries immediately; returns false if true/false No
lock unavailable.
tryLock(time, unit) Waits up to the given time, then true/false Up to timeout
gives up.
import [Link].*;
class Demo {
Lock lock = new ReentrantLock(true); // true = fairness policy ON
public void m1() throws InterruptedException {
if ([Link](10, [Link])) {
try {
[Link]("Task running...");
m2(); // Can call m2 — reentrant!
} finally {
[Link](); // Always unlock in finally
}
} else {
[Link]("Could not get lock, skipping...");
}
}
}
📝 Always call unlock() in a finally block to ensure the lock is released even if an exception occurs.
4.6 Fairness Policy
By default, synchronized does NOT guarantee fairness — a thread may starve waiting for a lock while
other threads keep jumping ahead.
• synchronized: No fairness policy. Thread selection is OS/JVM dependent.
• ReentrantLock(true): Enables fairness — threads get the lock in the order they requested it (FIFO).
• ReentrantLock(false) or default: No fairness (better performance).
5. Deadlock
5.1 What is Deadlock?
Deadlock is a situation where two or more threads are permanently stuck, each waiting for a resource
held by the other. No thread can proceed.
📝 Example: Thread A locks Resource 1 and waits for Resource 2. Thread B locks Resource 2 and waits for
Resource 1. Both wait forever.
5.2 When Does Deadlock Happen?
• Thread A holds Lock 1 and needs Lock 2.
• Thread B holds Lock 2 and needs Lock 1.
• Neither releases their held lock → both wait forever.
• Also happens when a thread calls wait() and no one calls notify() on it.
5.3 How to Avoid Deadlock?
• Use a consistent lock ordering (always acquire locks in the same order).
• Use tryLock(timeout) from ReentrantLock so threads don't wait indefinitely.
• Avoid nested locks where possible.
• Use wait(timeoutMs) instead of wait() to prevent infinite waiting.
6. wait(), notify(), notifyAll()
These methods are defined in the Object class and are used for inter-thread communication. They
MUST be called from within a synchronized block or method.
6.1 How They Work
Method What Happens Lock Released? Wakes
wait() Current thread releases the lock Yes Nothing
and enters waiting state.
notify() Wakes one arbitrary waiting thread; No One thread
lock is NOT released immediately.
notifyAll() Wakes ALL waiting threads; only No All threads
one gets the lock at a time.
6.2 Producer-Consumer Example
class Consumer extends Thread {
private Stack postBox;
public void run() {
synchronized (postBox) {
if ([Link]()) {
[Link](10000); // Wait up to 10 seconds
}
[Link]("Consuming: " + [Link]());
}
}
}
class Producer extends Thread {
private Stack postBox;
public void run() {
synchronized (postBox) {
[Link]("Hello!");
[Link](); // Wake up the consumer
}
}
}
7. ExecutorService & Thread Pools
7.1 What is ExecutorService?
ExecutorService is a framework to manage threads efficiently. Instead of manually creating threads
(new Thread()) for every task, you submit tasks to an ExecutorService which manages a pool of
reusable threads.
• Without ExecutorService: 1000 tasks = 1000 threads created manually.
• With ExecutorService: A pool of (e.g.) 5 threads handles all 1000 tasks by reusing them.
7.2 Types of Thread Pools
Pool Type Creation Behavior Best For
Fixed Thread [Link](n) Creates exactly n Known
Pool threads. Extra tasks wait number of
in a queue. concurrent
tasks
Single Thread [Link]() Only 1 thread. Tasks Tasks that
Pool execute one by one must run in
(sequentially). order
Cached Thread [Link]() Creates threads on Many short-
Pool demand; reuses idle lived tasks
threads. Threads die
after 60s idle.
Work Stealing [Link]() Uses all CPU cores; idle CPU-
Pool threads steal work from intensive
busy ones (Java 8+). parallel
tasks
ExecutorService ex = [Link](3);
// Submit a Runnable (no return value)
[Link](() -> [Link]("Task running"));
// Submit a Callable (returns a value)
Future<String> future = [Link](() -> "Result from thread");
String result = [Link](); // Blocks until result is ready
[Link](); // Always shutdown after use
7.3 Callable & Future
Use Callable with ExecutorService when you need a return value from the thread. The Future object
holds the eventual result.
Callable<Integer> task = () -> {
[Link](1000);
return 42;
};
ExecutorService ex = [Link](2);
Future<Integer> future = [Link](task);
// Do other work here while task runs in background...
[Link]("Working...");
Integer result = [Link](5, [Link]); // Wait max 5s
[Link]("Result: " + result);
[Link]();
7.4 ScheduledExecutorService
A special ExecutorService for scheduling tasks to run after a delay or repeatedly at fixed intervals (like
timers, cron jobs, alarms).
Method When Task Runs Next
schedule(task, delay, unit) Once, after the given delay
scheduleAtFixedRate(task, initial, period, Every 'period' time from the START of the previous run
unit) (fixed rate)
scheduleWithFixedDelay(task, initial, delay, After 'delay' from the END of the previous run (fixed
unit) delay)
ScheduledExecutorService service = [Link](1);
// Run once after 3 seconds
[Link](() -> [Link]("Delayed task"), 3, [Link]);
// Run every 2 seconds (fixed rate)
[Link](
() -> [Link]("Fixed rate task"),
1, // initial delay
2, // period
[Link]
);
8. ThreadLocal
8.1 What is ThreadLocal?
ThreadLocal is a class in Java that provides thread-local variables. Each thread has its own isolated
copy of the variable — no sharing, no conflicts.
• Problem it solves: If multiple threads share a field (e.g., username), one thread's write can corrupt
another thread's data.
• Solution: With ThreadLocal, each thread reads and writes its own copy.
8.2 Common Use Cases
• Storing per-request user session info in web applications.
• Per-thread database connection or transaction context.
• Objects that are not thread-safe (e.g., SimpleDateFormat).
ThreadLocal<String> threadLocal = new ThreadLocal<>();
// Thread 1
[Link]("user-john");
[Link]([Link]()); // "user-john" — only for Thread 1
// Thread 2
[Link]("user-alice");
[Link]([Link]()); // "user-alice" — only for Thread 2
// Always remove after use (especially in thread pools!)
[Link]();
⚠️ Always call [Link]() after use — especially in thread pools! Pooled threads are reused, and
stale ThreadLocal values can cause memory leaks and data leakage between requests.
9. @Async in Spring Boot
9.1 What is @Async?
@Async is a Spring annotation that allows a method to run in a separate thread using Spring's
managed thread pool. The caller does not block — it continues immediately while the method runs in
the background.
9.2 How to Enable
• Add @EnableAsync on your main Spring Boot class.
• Add @Async on any method you want to run asynchronously.
@SpringBootApplication
@EnableAsync // Step 1: Enable async support
public class MyApp { ... }
@Service
public class EmailService {
@Async // Step 2: This method runs in a background thread
public void sendEmail(String to) {
// Long-running email send logic
[Link]("Sending email to: " + to);
}
}
Without @Async With @Async
sendEmail() must finish before next line sendEmail() runs in background thread
Caller thread is blocked Caller continues immediately
Sequential execution Parallel execution
10. Locking Mechanisms in Java
Lock Type Package Best For
synchronized (method/block) [Link] Simple thread safety for a single
resource.
ReentrantLock [Link] Advanced control: tryLock, fairness,
lock in one method/unlock in another.
ReentrantReadWriteLock [Link] Multiple readers can read
simultaneously; writers get exclusive
access.
StampedLock [Link] Optimistic reading — fast reads without
full locking (Java 8+).
11. Interview Questions & Answers
Q1. What is a thread? What is multithreading?
A thread is the smallest unit of execution in Java. Multithreading is the ability to run multiple threads
simultaneously, used to execute independent tasks in parallel for better performance.
Q2. How many ways can you create a thread in Java?
• Extending the Thread class
• Implementing the Runnable interface (preferred)
• Implementing the Callable interface (when you need a return value)
• Using Lambda expressions (Java 8+)
• Using @Async annotation (Spring Boot)
Q3. Why is Runnable preferred over extending Thread?
Because implementing Runnable allows you to still extend another class (Java supports multiple
interface implementation but not multiple class inheritance). It also separates the task definition from
thread management.
Q4. What is the difference between run() and start()?
• run(): Contains the thread's task logic. Calling run() directly executes it on the current thread — no
new thread is created.
• start(): Creates a new thread, registers it with the Thread Scheduler, and eventually calls run() in
the new thread.
Q5. What is the synchronized keyword? What are its disadvantages?
synchronized ensures only one thread at a time can access a method or block. It uses object-level lock
(non-static) or class-level lock (static).
Disadvantages: performance overhead (only one thread at a time), risk of deadlock, thread starvation
(no fairness guarantee).
Q6. Synchronized method vs synchronized block — which is preferred?
Synchronized block is preferred. It locks only the critical section of code instead of the entire method,
reducing contention and improving performance.
Q7. What is the difference between object-level and class-level lock?
• Object-level lock: Used for non-static synchronized methods/blocks. Each instance has its own lock.
Multiple threads can access the same method on different objects simultaneously.
• Class-level lock: Used for static synchronized methods/blocks. Shared across ALL instances. Only
one thread can execute at a time regardless of which object is used.
Q8. What is a deadlock? When does it occur? How do you avoid it?
Deadlock: Two or more threads are permanently blocked waiting for each other's locks.
Occurs when: Thread A holds Lock 1 and waits for Lock 2; Thread B holds Lock 2 and waits for Lock 1.
Avoid by: consistent lock ordering, using tryLock() with timeout, avoiding nested locks, using
wait(timeout) instead of wait().
Q9. What is fairness policy in multithreading?
Fairness ensures threads get the lock in the order they requested it (FIFO). synchronized does NOT
support fairness. ReentrantLock supports fairness when created as new ReentrantLock(true). Without
fairness, some threads may starve.
Q10. What is ReentrantLock? How is it different from synchronized?
Feature synchronized ReentrantLock
Fairness No Yes (with true param)
Try to acquire No tryLock()
Timeout No tryLock(time, unit)
Lock in one method, unlock in No Yes
another
Automatic release on exception Yes No (must use finally)
Q11. What is wait()? How is it different from sleep()?
Feature wait() sleep()
Defined in Object class Thread class
Releases lock? Yes No
Must be in Yes No
synchronized?
Woken by notify() / notifyAll() Time expiry or interrupt
Purpose Inter-thread communication Pause execution for time
Q12. What is ExecutorService? What is a thread pool?
ExecutorService is a framework that manages a pool of reusable threads. Instead of creating a new
thread for every task (wasteful and slow), you submit tasks to the pool and threads are reused. Types:
FixedThreadPool, CachedThreadPool, SingleThreadExecutor, WorkStealingPool.
Q13. Difference between FixedThreadPool and CachedThreadPool?
• FixedThreadPool: Fixed number of threads; extra tasks queue up. Good for controlling max
concurrency.
• CachedThreadPool: Threads created on demand; idle threads reused; die after 60 seconds. Good
for many short-lived tasks.
Q14. What is ThreadLocal? Why do we need it?
ThreadLocal provides per-thread storage — each thread has its own isolated copy of a variable. Used
to avoid data inconsistency when multiple threads access shared code (e.g., storing per-request user
session in web apps). Always call remove() to prevent memory leaks in thread pools.
Q15. What is the difference between notify() and notifyAll()?
• notify(): Wakes exactly ONE waiting thread (chosen arbitrarily by JVM).
• notifyAll(): Wakes ALL waiting threads. Only one gets the lock at a time; others go back to waiting.
• Use notifyAll() when you want all threads to re-evaluate their condition.
Q16. What is the volatile keyword?
volatile guarantees that a variable's value is always read from and written to main memory (not CPU
cache). This ensures visibility of changes across threads. It does NOT make compound operations (like
i++) atomic.
Q17. What is a daemon thread?
A daemon thread is a background/helper thread (e.g., Garbage Collector) that runs in the background.
The JVM terminates daemon threads automatically when all non-daemon (user) threads finish. The
main thread is NOT a daemon thread.
Q18. What is Callable and Future?
Callable is an interface (like Runnable) that can return a value and throw checked exceptions. Submit a
Callable to an ExecutorService and you get a Future<T>. Call [Link]() to retrieve the result (blocks
until ready). You can also set a timeout: [Link](5, [Link]).
Q19. What is scheduleAtFixedRate vs scheduleWithFixedDelay?
• scheduleAtFixedRate: Next task starts at a fixed interval from the START of the previous task. If a
task takes longer than the interval, the next run is immediate.
• scheduleWithFixedDelay: Next task starts after a fixed DELAY from the END of the previous task.
Guarantees a gap between completions.
Q20. What are the locking mechanisms in Java?
• synchronized (method or block) — simplest
• ReentrantLock — advanced features: tryLock, fairness
• ReentrantReadWriteLock — separate read/write locks
• StampedLock — optimistic locking (fastest reads)
Java Multithreading Complete Notes • Covers all standard interview topics