Multithreading & Concurrency in Java: One-Stop Solution for Interviews
Introduction Multithreading and concurrency are fundamental concepts in Java, crucial for building high-performance and scalable applications. This guide covers everything you need to master Java concurrency for interviews, including essential concepts, code snippets, and best practices. 1. Thread vs. Runnable Thread Class Java provides the Thread class, which represents a thread of execution. To create a thread, extend the Thread class and override the run() method. Example: class MyThread extends Thread { public void run() { System.out.println("Thread is running"); } } public class ThreadExample { public static void main(String[] args) { MyThread t1 = new MyThread(); t1.start(); // Starts a new thread } } Runnable Interface Java provides the Runnable interface, which represents a task that can be executed by a thread. Runnable is preferred as Java does not support multiple inheritance. Example: class MyRunnable implements Runnable { public void run() { System.out.println("Runnable is running"); } } public class RunnableExample { public static void main(String[] args) { Thread t1 = new Thread(new MyRunnable()); t1.start(); // Starts a new thread } } Key Differences Feature Thread Class Runnable Interface Inheritance Extends Thread Implements Runnable Flexibility Less flexible (restricts inheritance) More flexible (allows extending other classes) Resource Usage More memory overhead Less memory overhead Best Practice Not recommended Recommended 2. synchronized Keyword, Locks, and Reentrant Locks synchronized Keyword Used to achieve mutual exclusion. Ensures only one thread can execute a synchronized block/method at a time. Example: class SharedResource { synchronized void printNumbers() { for (int i = 1; i { synchronized (lock1) { synchronized (lock2) { System.out.println("Thread 1 acquired locks"); } } }); Thread t2 = new Thread(() -> { synchronized (lock2) { synchronized (lock1) { System.out.println("Thread 2 acquired locks"); } } }); t1.start(); t2.start(); } } Livelock Occurs when threads keep changing state to avoid deadlock but never make progress. 5. Fork/Join Framework Used for parallel processing, dividing tasks into smaller subtasks. Uses ForkJoinPool and RecursiveTask. Example: import java.util.concurrent.RecursiveTask; import java.util.concurrent.ForkJoinPool; class SumTask extends RecursiveTask { private int start, end; SumTask(int start, int end) { this.start = start; this.end = end; } protected Integer compute() { if (end - start

Introduction
Multithreading and concurrency are fundamental concepts in Java, crucial for building high-performance and scalable applications. This guide covers everything you need to master Java concurrency for interviews, including essential concepts, code snippets, and best practices.
1. Thread vs. Runnable
Thread Class
- Java provides the
Thread
class, which represents a thread of execution. - To create a thread, extend the
Thread
class and override therun()
method.
Example:
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // Starts a new thread
}
}
Runnable Interface
- Java provides the
Runnable
interface, which represents a task that can be executed by a thread. - Runnable is preferred as Java does not support multiple inheritance.
Example:
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable is running");
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start(); // Starts a new thread
}
}
Key Differences
Feature | Thread Class | Runnable Interface |
---|---|---|
Inheritance | Extends Thread
|
Implements Runnable
|
Flexibility | Less flexible (restricts inheritance) | More flexible (allows extending other classes) |
Resource Usage | More memory overhead | Less memory overhead |
Best Practice | Not recommended | Recommended |
2. synchronized
Keyword, Locks, and Reentrant Locks
synchronized
Keyword
- Used to achieve mutual exclusion.
- Ensures only one thread can execute a synchronized block/method at a time.
Example:
class SharedResource {
synchronized void printNumbers() {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
class MyThread extends Thread {
SharedResource resource;
MyThread(SharedResource res) { this.resource = res; }
public void run() { resource.printNumbers(); }
}
public class SynchronizedExample {
public static void main(String[] args) {
SharedResource obj = new SharedResource();
MyThread t1 = new MyThread(obj);
MyThread t2 = new MyThread(obj);
t1.start();
t2.start();
}
}
Locks and ReentrantLock
-
ReentrantLock
allows more control thansynchronized
, including fairness policies. - Supports try-lock and timed locking mechanisms.
Example:
import java.util.concurrent.locks.ReentrantLock;
class SharedResource {
private final ReentrantLock lock = new ReentrantLock();
void printNumbers() {
lock.lock();
try {
for (int i = 1; i <= 5; i++) {
System.out.println(i);
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3. volatile
, Atomic Variables, and ThreadLocal
volatile
Keyword
- Ensures visibility of changes to variables across threads.
- Prevents instruction reordering.
Example:
class SharedData {
volatile boolean flag = false;
}
Atomic Variables
- Provided by
java.util.concurrent.atomic
package. - Ensures atomic updates without explicit synchronization.
Example:
import java.util.concurrent.atomic.AtomicInteger;
class Counter {
private AtomicInteger count = new AtomicInteger(0);
void increment() { count.incrementAndGet(); }
}
ThreadLocal
- Provides thread-local variables, ensuring thread safety without synchronization.
Example:
class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
}
4. Thread Starvation, Deadlock, and Livelock
Thread Starvation
Occurs when low-priority threads never get CPU time due to higher-priority threads monopolizing resources.
Deadlock
Occurs when two or more threads wait indefinitely for each other to release a lock.
Example:
class DeadlockExample {
static final Object lock1 = new Object();
static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
synchronized (lock2) {
System.out.println("Thread 1 acquired locks");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
synchronized (lock1) {
System.out.println("Thread 2 acquired locks");
}
}
});
t1.start();
t2.start();
}
}
Livelock
Occurs when threads keep changing state to avoid deadlock but never make progress.
5. Fork/Join Framework
- Used for parallel processing, dividing tasks into smaller subtasks.
- Uses
ForkJoinPool
andRecursiveTask
.
Example:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
class SumTask extends RecursiveTask<Integer> {
private int start, end;
SumTask(int start, int end) { this.start = start; this.end = end; }
protected Integer compute() {
if (end - start <= 10) {
int sum = 0;
for (int i = start; i <= end; i++) sum += i;
return sum;
}
int mid = (start + end) / 2;
SumTask left = new SumTask(start, mid);
SumTask right = new SumTask(mid + 1, end);
left.fork();
return right.compute() + left.join();
}
}
public class ForkJoinExample {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
int result = pool.invoke(new SumTask(1, 100));
System.out.println("Sum: " + result);
}
}
Conclusion
Mastering multithreading and concurrency in Java is essential for building efficient applications. Understanding synchronized
, locks, volatile
, atomic variables, deadlocks, and the Fork/Join framework will help you excel in interviews and real-world scenarios.