Multithreading in Java
A comprehensive guide to Java multithreading with detailed code examples and explanations covering fundamental to advanced concurrency concepts.
Thread Basics
What is a Thread?
A thread is the smallest unit of execution within a process. Java provides built-in support for multithreaded programming through the Thread class and Runnable interface.
Creating Threads
class ThreadUsingThreadClass extends Thread {
public ThreadUsingThreadClass(String name) {
super(name); // Set thread name
}
@Override
public void run() {
System.out.println("This thread is created via thread class....");
}
}
// Usage
ThreadUsingThreadClass thread = new ThreadUsingThreadClass("MyThread");
thread.start(); // Always call start(), not run()Key Points:
- Extend the
Threadclass - Override the
run()method - Call
start()method to begin execution (notrun()) - Allows only single inheritance
class ThreadUsingRunnableInterface implements Runnable {
@Override
public void run() {
System.out.println("This thread is created via Runnable interface....");
for(int i = 0; i < 10000; i++) {
System.out.println("Thread " + Thread.currentThread().getName()
+ " called " + i + " time");
}
}
}
// Usage
Thread thread = new Thread(new ThreadUsingRunnableInterface(), "MyRunnable");
thread.start();Key Points:
- Implement the
Runnableinterface - Override the
run()method - Preferred in modern Java (allows implementation of other interfaces)
- Thread object is created separately
Basic Example
public class Main {
public static void main(String args[]) {
// Create threads using both methods
ThreadUsingThreadClass thread1 = new ThreadUsingThreadClass("Thread1");
ThreadUsingRunnableInterface threadUsingRunnableInterface =
new ThreadUsingRunnableInterface();
Thread thread2 = new Thread(threadUsingRunnableInterface, "Thread2");
thread1.start();
thread2.start();
}
}Thread Lifecycle
A thread in Java goes through 5 different states during its lifecycle:
NEW
Thread is created but not yet started
RUNNABLE
Thread is ready to run and waiting for CPU time
RUNNING
Thread is actively executing
BLOCKED/WAITING
Thread is waiting for a resource or another thread
TERMINATED
Thread has finished execution
Code Example
public class ThreadLifeCycle extends Thread {
@Override
public void run() {
System.out.println("Running....");
try {
Thread.sleep(2000); // Transition to TIMED_WAITING
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
public static void main(String args[]) {
ThreadLifeCycle thread1 = new ThreadLifeCycle();
System.out.println(thread1.getState()); // NEW
thread1.start(); // RUNNABLE
System.out.println(thread1.getState());
try {
Thread.sleep(100); // Allow thread to transition
System.out.println(thread1.getState()); // TIMED_WAITING
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
try {
thread1.join(); // Wait for thread to complete
System.out.println(thread1.getState()); // TERMINATED
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}Thread Types
User Threads (Non-Daemon Threads)
- Responsible for executing the main logic of the application
- JVM waits for all user threads to complete before terminating
- Application will not shut down as long as a user thread is running
- Created by default when you create new threads
Daemon Threads
- Low-priority threads that provide background services (e.g., garbage collection, finalizers)
- JVM does not wait for daemon threads before shutting down
- Automatically terminated when all user threads are finished
- Used for housekeeping or monitoring activities
Code Example
public class ThreadTypes extends Thread {
@Override
public void run() {
while(true) {
System.out.println("Thread running.......");
}
}
public static void main(String[] args) {
ThreadTypes thread1 = new ThreadTypes();
ThreadTypes thread2 = new ThreadTypes();
thread1.setDaemon(true); // Set thread1 as daemon
thread1.start(); // Will not prevent JVM shutdown
thread2.start(); // User thread (prevents JVM shutdown)
System.out.println("Main Done");
}
}| Feature | User Thread | Daemon Thread |
|---|---|---|
| JVM Wait | Yes | No |
| Priority | Normal | Low |
| Examples | Main thread, custom tasks | GC, finalizers |
| Termination | Explicit | Automatic |
Thread Methods
Important Thread Methods
Complete Example
public class ThreadMethods extends Thread {
public static void main(String args[]) {
ThreadMethods thread1 = new ThreadMethods();
ThreadMethods thread2 = new ThreadMethods();
// Set priorities
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
// Interrupt a thread
ThreadLifeCycle t1 = new ThreadLifeCycle();
t1.start();
t1.interrupt(); // Interrupt the thread
}
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " is running");
Thread.yield(); // Yield to other threads
}
}
}Synchronization
Problem: Race Condition
When multiple threads access shared resources without proper synchronization, inconsistencies occur.
Solution: Synchronized Keyword
The synchronized keyword prevents concurrent access to shared resources.
class Counter {
private int count = 0;
public synchronized void increment() {
count++; // Only one thread can execute this at a time
}
public synchronized void decrement() {
count--;
}
public int getCount() {
return count;
}
}class Counter {
private int count = 0;
public void increment() {
synchronized(this) { // Critical section
count++;
}
}
}Complete Example
public class Synchronization extends Thread {
private final Counter counter;
public Synchronization(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for(int i = 1; i <= 1000; i++) {
counter.increment();
}
}
public static void main(String[] args) {
Counter counter = new Counter();
Synchronization t1 = new Synchronization(counter);
Synchronization t2 = new Synchronization(counter);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
System.out.println(counter.getCount()); // Output: 2000
}
}How Synchronization Works
Only one thread can execute synchronized code at a time
Other threads are blocked and enter a wait queue
When lock is released, one thread from queue is chosen
Ensures mutual exclusion and prevents race conditions
Key Points:
- Synchronized method locks the entire object
- Synchronized block locks only the critical section
- Every object has an intrinsic lock (monitor)
- Helps achieve mutual exclusion and memory visibility
Locks
Intrinsic Locks vs Explicit Locks
- Used with
synchronizedkeyword - Automatically acquired and released
- Simple but less flexible
- Provide more control and flexibility
- Manual acquisition and release
- Allow advanced features like fairness and timeouts
Types of Locks
Synchronization Utilities
1. CountDownLatch
Allows one or more threads to wait until a set of operations complete.
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numberOfServices = 3;
CountDownLatch latch = new CountDownLatch(numberOfServices);
ExecutorService executorService = Executors.newFixedThreadPool(numberOfServices);
executorService.submit(new DependentService("Database", latch));
executorService.submit(new DependentService("Cache", latch));
executorService.submit(new DependentService("Messaging", latch));
latch.await(); // Wait for all services to complete
System.out.println("All services initialized. Starting application...");
executorService.shutdown();
}
}
class DependentService implements Callable<String> {
private final String serviceName;
private final CountDownLatch latch;
public DependentService(String serviceName, CountDownLatch latch) {
this.serviceName = serviceName;
this.latch = latch;
}
@Override
public String call() throws Exception {
try {
Thread.sleep(2000); // Simulate initialization
System.out.println(serviceName + " is initialized.");
} finally {
latch.countDown(); // Decrement count
}
return serviceName + " is ready.";
}
}Key Points:
- One-time use only (not reusable)
- Useful for startup scenarios
countDown()decrements countawait()blocks until count reaches zero
Use Cases
- Waiting for multiple services to initialize
- Running tests in parallel with main thread waiting
- Coordinating startup sequences
2. CyclicBarrier
Allows a set of threads to wait for each other at a barrier point. Reusable.
public class CyclicBarrierExample {
public static void main(String[] args) {
int numberOfSubSystems = 4;
CyclicBarrier barrier = new CyclicBarrier(numberOfSubSystems, () -> {
System.out.println("All subsystems are ready. Proceeding...");
});
Thread dbThread = new Thread(new SubSystem("Database", 2000, barrier));
Thread cacheThread = new Thread(new SubSystem("Cache", 3000, barrier));
Thread messagingThread = new Thread(new SubSystem("Messaging", 4000, barrier));
Thread apiThread = new Thread(new SubSystem("API", 2500, barrier));
dbThread.start();
cacheThread.start();
messagingThread.start();
apiThread.start();
}
}
class SubSystem implements Runnable {
private final String name;
private final int initializationTime;
private final CyclicBarrier barrier;
public SubSystem(String name, int initializationTime, CyclicBarrier barrier) {
this.name = name;
this.initializationTime = initializationTime;
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(name + " is initializing...");
Thread.sleep(initializationTime);
System.out.println(name + " has completed initialization.");
barrier.await(); // Wait for all threads
System.out.println(name + " is proceeding with main task.");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
}
}Key Points:
- Reusable - can be used multiple times
- All threads wait at barrier point
- When all threads arrive, they proceed together
- Optional action runs when all threads meet
CountDownLatch vs CyclicBarrier
| Feature | CountDownLatch | CyclicBarrier |
|---|---|---|
| Reusable | No | Yes |
| Direction | One-way | Bidirectional |
| Flexibility | Less | More |
| Use Case | Wait for tasks | Synchronize tasks |
Deadlocks
What is a Deadlock?
A deadlock occurs when two or more threads are blocked forever, waiting for each other.
Conditions for Deadlock (All 4 must be true)
Mutual Exclusion - Only one thread can access a resource at a time
Hold and Wait - Thread holding a resource waits for another
No Preemption - Resources cannot be forcibly taken
Circular Wait - Circular chain of threads waiting for resources
Example of Deadlock
class Pen {
public synchronized void writeWithPenAndPaper(Paper paper) {
System.out.println(Thread.currentThread().getName()
+ " is using pen and trying to write");
paper.finishWriting(); // Waits for paper lock
}
public synchronized void finishWriting() {
System.out.println(Thread.currentThread().getName() + " finished using pen");
}
}
class Paper {
public synchronized void writeWithPaperAndPen(Pen pen) {
System.out.println(Thread.currentThread().getName()
+ " is using paper and trying to write");
pen.finishWriting(); // Waits for pen lock
}
public synchronized void finishWriting() {
System.out.println(Thread.currentThread().getName() + " finished using paper");
}
}
class Task1 implements Runnable {
private Pen pen;
private Paper paper;
public Task1(Pen pen, Paper paper) {
this.pen = pen;
this.paper = paper;
}
@Override
public void run() {
pen.writeWithPenAndPaper(paper); // Locks pen, waits for paper
}
}
class Task2 implements Runnable {
private Pen pen;
private Paper paper;
public Task2(Pen pen, Paper paper) {
this.pen = pen;
this.paper = paper;
}
@Override
public void run() {
paper.writeWithPaperAndPen(pen); // Locks paper, waits for pen → DEADLOCK!
}
}
public class DeadLockExample {
public static void main(String[] args) {
Pen pen = new Pen();
Paper paper = new Paper();
Thread thread1 = new Thread(new Task1(pen, paper), "Thread-1");
Thread thread2 = new Thread(new Task2(pen, paper), "Thread-2");
thread1.start();
thread2.start();
}
}Prevention Methods
class Task2 implements Runnable {
private Pen pen;
private Paper paper;
@Override
public void run() {
// Always acquire locks in same order
synchronized(pen) {
paper.writeWithPaperAndPen(pen); // No deadlock
}
}
}if(lock1.tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
if(lock2.tryLock(1000, TimeUnit.MILLISECONDS)) {
// Proceed
}
} finally {
lock2.unlock();
}
} finally {
lock1.unlock();
}Minimize the use of nested synchronized blocks.
Deadlock Prevention Strategies
- Hold and Wait - Acquire all locks at once
- No Preemption - Use tryLock() with timeout
- Circular Wait - Enforce lock ordering
- Mutual Exclusion - Not preventable in most cases
Executor Framework
What is Executor Framework?
A high-level abstraction for managing threads and executing tasks efficiently. Introduced in Java 5 as part of java.util.concurrent.
Components
Executor - Core interface for executing tasks
ExecutorService - Extended interface with lifecycle management
ThreadPoolExecutor - Implementation managing pool of threads
ScheduledExecutorService - For scheduling tasks
Executors - Utility class with factory methods
Future - Represents async computation result
Callable - Task that returns a value
Code Example
public class ExecutorFramework {
public static void main(String[] args) {
// Without Executor Framework
long startTime = System.currentTimeMillis();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
int finalI = i;
threads[i] = new Thread(() -> {
long result = factorial(finalI);
System.out.println("Factorial of " + finalI + " is: " + result);
});
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Time without Framework: "
+ (System.currentTimeMillis() - startTime) + " ms");
// With Executor Framework
startTime = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(9);
for (int i = 0; i < 10; i++) {
int finalI = i;
executorService.submit(() -> {
long result = factorial(finalI);
System.out.println("Factorial of " + finalI + " is: " + result);
});
}
executorService.shutdown();
while (!executorService.isTerminated()) {
// Wait for completion
}
System.out.println("Time with Framework: "
+ (System.currentTimeMillis() - startTime) + " ms");
}
static long factorial(long n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
}Types of ExecutorService
Using ExecutorService
Fire and forget - no return value
executorService.execute(() -> System.out.println("Task executed"));Returns Future for tracking
Future<?> future = executorService.submit(() -> System.out.println("Task"));
System.out.println(future.isDone());Submit multiple Callables
List<Callable<Integer>> tasks = Arrays.asList(
() -> factorial(5),
() -> factorial(6),
() -> factorial(7)
);
List<Future<Integer>> results = executorService.invokeAll(tasks);Benefits of Executor Framework
- Thread pooling reduces overhead
- Better resource management
- Cleaner code
- Built-in scheduling support
- Task cancellation support
Asynchronous Programming
CompletableFuture
Introduced in Java 8 for easier asynchronous and functional programming.
public class CompletableFutureExample {
public static void main(String[] args) {
// Create async computation
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Task interrupted");
}
return "Hello, World!";
});
// Wait and get result
String result = future.join(); // Blocks until complete
System.out.println("Result: " + result);
// Alternative methods
try {
result = future.get(); // Also blocks
System.out.println("Result using get(): " + result);
result = future.getNow("default"); // Non-blocking
System.out.println("Result using getNow(): " + result);
result = future.get(3, TimeUnit.SECONDS); // With timeout
System.out.println("Result with timeout: " + result);
CompletableFuture.allOf().thenApply(v -> "All tasks completed").get();
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new RuntimeException(e);
}
}
}Key Features
supplyAsync()- Execute async taskjoin()- Block and wait for resultget()- Block with exception handlinggetNow()- Non-blocking, returns default if not readyallOf()- Combine multiple futuresthenApply()- Chain operationsthenAccept()- Consume resultexceptionally()- Handle exceptions
Volatile vs Atomic
Volatile Keyword
Ensures visibility of changes across threads but NOT atomicity.
class Shared {
private volatile boolean flag = false; // Visible to all threads
public void setFlagTrue() {
System.out.println("Setting flag to true");
flag = true;
}
public void printIfFlagTrue() {
while (!flag) {
// Busy-wait - volatile ensures we see updated value
}
System.out.println("Flag is true!");
}
}
public class VolatileVsAtomic {
public static void main(String[] args) {
Shared shared = new Shared();
Thread writerThread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shared.setFlagTrue();
});
Thread readerThread = new Thread(() -> {
shared.printIfFlagTrue();
});
readerThread.start();
writerThread.start();
}
}Atomic Classes
Provides atomic operations with both visibility and atomicity.
class Counter {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet(); // Atomic increment
}
public int getValue() {
return counter.get();
}
}
public class AtomicExample {
public static void main(String[] args) {
Counter counter = new 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) {
Thread.currentThread().interrupt();
}
System.out.println("Final Counter Value: " + counter.getValue()); // 2000
}
}Comparison
| Feature | Volatile | Atomic |
|---|---|---|
| Visibility | Yes | Yes |
| Atomicity | No | Yes |
| Performance | High | Slightly Lower |
| Use Case | Flags, simple vars | Counters, complex ops |
| Thread Safety | Partial | Complete |
Common Atomic Classes
AtomicInteger- Atomic intAtomicLong- Atomic longAtomicBoolean- Atomic booleanAtomicReference<T>- Atomic object referenceAtomicIntegerArray- Atomic array of ints
Thread Communication
Problem
How do threads communicate and coordinate execution?
Solution: wait(), notify(), notifyAll()
Used for inter-thread communication through shared object monitors.
class SharedResource {
private int data;
private boolean hasData;
public synchronized void produce(int value) {
while(hasData) {
try {
wait(); // Release lock and wait
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}
data = value;
hasData = true;
System.out.println("Data Produced");
notify(); // Wake up one waiting thread
}
public synchronized int consume() {
while(!hasData) {
try {
wait(); // Release lock and wait
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}
hasData = false;
System.out.println("Data Consumed");
notify(); // Wake up one waiting thread
return data;
}
}
class Producer implements Runnable {
private SharedResource resource;
public Producer(SharedResource resource) {
this.resource = resource;
}
@Override
public void run() {
for(int i = 0; i < 10; i++) {
resource.produce(i);
}
}
}
class Consumer implements Runnable {
private SharedResource resource;
public Consumer(SharedResource resource) {
this.resource = resource;
}
@Override
public void run() {
for(int i = 0; i < 10; i++) {
int value = resource.consume();
}
}
}
public class ThreadCommunicationExample {
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();
}
}Methods
Producer-Consumer Pattern
This is the classic use case for thread communication.
Producer:
- Check if data exists
- If yes, wait() for consumer to consume
- Produce data
- notify() consumer
- Repeat
Consumer:
- Check if data exists
- If no, wait() for producer
- Consume data
- notify() producer
- Repeat
Key Points
- Always check condition in while loop (not if)
- Always call notify() when done
- Use notifyAll() when multiple consumers/producers
- Improves performance vs busy-waiting
Summary of Key Concepts
Thread Creation
Direct inheritance - simple but limits extensibility
Preferred approach - allows implementation of other interfaces
Thread Management
- start() vs run() - Always call start()
- Thread states - NEW, RUNNABLE, RUNNING, BLOCKED, TERMINATED
- Thread types - User threads vs Daemon threads
Synchronization Techniques
Synchronized keyword - Intrinsic locks
Locks - ReentrantLock, ReadWriteLock
Synchronization utilities - CountDownLatch, CyclicBarrier
Thread communication - wait(), notify()
Deadlock Prevention
- Ordered lock acquisition
- Lock timeouts
- Avoid nested locks
High-Level APIs
- Executor Framework - Manage thread pools
- CompletableFuture - Asynchronous programming
- Atomic classes - Atomic operations
Best Practices
Recommended Practices
✓ Use Runnable over Thread class
✓ Use Executor Framework for thread management
✓ Prefer CompletableFuture for async tasks
✓ Use volatile for simple flags
✓ Use Atomic classes for counters
✓ Always handle InterruptedException
✓ Use locks instead of synchronized for complex scenarios
✓ Always release locks in finally blocks
✓ Avoid busy-waiting
✓ Always call shutdown() on ExecutorService
Interview Tips
Quick Reference
Creating Threads
// Method 1
Thread t = new MyThread();
t.start();
// Method 2
Thread t = new Thread(() -> System.out.println("Running"));
t.start();Common Methods
thread.start(); // Start thread
thread.join(); // Wait for completion
thread.interrupt(); // Interrupt thread
thread.setPriority(10); // Set priority
Thread.sleep(1000); // Current thread sleep
Thread.yield(); // Yield to othersSynchronization
// Synchronized method
public synchronized void method() { }// Synchronized block
synchronized(lock) { }// Lock
Lock lock = new ReentrantLock();
lock.lock();
try { } finally { lock.unlock(); }Executor Framework
ExecutorService exec = Executors.newFixedThreadPool(10);
exec.submit(() -> System.out.println("Task"));
exec.shutdown();Last Updated: December 2024
Package: com.java.interview.multithreading