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.
Top comments (0)