Java Multithreading Interview QnA
Multithreading is a core topic in Java, often appearing in interviews. Let’s dive deeper into common questions, their answers, and practical examples.
📌 What is a thread, and how does it differ from a process?
A thread is the smallest unit of execution within a process. Threads within the same process share memory, whereas processes are independent and have separate memory spaces.
Example:
In a text editor, one thread might handle user inputs while another checks spelling in the background.
// Thread example
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running...");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // Starts a new thread
}
}
📌 How do you prevent deadlocks in multithreaded applications?
Deadlocks occur when two or more threads are waiting for each other to release locks, leading to a stalemate. You can avoid them by:
- Acquiring locks in a consistent order.
- Using timeouts when acquiring locks.
- Avoiding nested locks whenever possible.
Example:
// Example of consistent lock ordering to prevent deadlocks
class SharedResource {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
System.out.println("Method1 is executing");
}
}
}
public void method2() {
synchronized (lock1) { // Consistent lock ordering
synchronized (lock2) {
System.out.println("Method2 is executing");
}
}
}
}
📌 What is the difference between synchronized
and ReentrantLock
in Java?
synchronized
is simpler to use but lacks advanced features. The lock is released automatically when the synchronized block exits.ReentrantLock
provides additional features such as lock polling, timed lock waits, and interruptible lock acquisition.
Example:
// ReentrantLock example
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void printMessage() {
if (lock.tryLock()) { // Attempts to acquire the lock
try {
System.out.println("Lock acquired. Processing...");
} finally {
lock.unlock(); // Always release the lock
}
} else {
System.out.println("Could not acquire lock");
}
}
}
📌 What are the advantages of using a Callable
over Runnable
?
Callable
allows tasks to return a result and throw checked exceptions, unlike Runnable
. It is especially useful when you need to fetch computation results from threads.
Example:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 123; // Task result
};
Future<Integer> future = executor.submit(task);
System.out.println("Result: " + future.get()); // Blocks until result is ready
executor.shutdown();
}
}
📌 How do you handle thread-safety for a shared resource?
Thread-safety ensures that multiple threads can access a resource without corrupting its state. You can achieve this using:
- Synchronization (
synchronized
orReentrantLock
). - Atomic classes (
AtomicInteger
,AtomicLong
).
Example:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
public static void main(String[] args) {
AtomicExample example = new AtomicExample();
example.increment();
System.out.println("Count: " + example.getCount());
}
}
📌 What is the purpose of the volatile
keyword in Java multithreading?
volatile
ensures visibility of changes to a variable across threads by preventing thread-local caching. It is ideal for simple flags but doesn’t provide atomicity.
Example:
public class VolatileExample {
private volatile boolean running = true;
public void stop() {
running = false; // Changes visible across threads
}
public void runTask() {
while (running) {
System.out.println("Running...");
}
}
}
📌 How do you optimize the performance of a multithreaded Java application?
- Use thread pools (
ExecutorService
) instead of manually creating threads. - Minimize contention for shared resources by reducing synchronized blocks.
- Use non-blocking data structures like
ConcurrentHashMap
.
Example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.submit(() -> System.out.println("Task " + taskId + " is running"));
}
executor.shutdown();
}
}
These questions form a solid foundation for understanding Java multithreading.
If you want to explore more detailed use cases, follow me and subscribe to my YouTube channel codefarm for real-world Java examples and interview tips.