Java Multithreading Interview QnA

Arvind Kumar
3 min readDec 6, 2024

--

https://youtube.com/@codefarm0

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:

  1. Acquiring locks in a consistent order.
  2. Using timeouts when acquiring locks.
  3. 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:

  1. Synchronization (synchronized or ReentrantLock).
  2. 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.

--

--

Arvind Kumar
Arvind Kumar

Written by Arvind Kumar

Staff Engineer @Chegg || Passionate about technology || https://youtube.com/@codefarm0

No responses yet