Multithreading: Essential Coding Questions for Interviews
Multithreading is a programming concept that enables concurrent execution of multiple threads within a single process, improving performance and responsiveness. It is widely used in Java, Python, and other languages to handle tasks like parallel processing and resource sharing efficiently.
Understanding multithreading is crucial for optimizing applications and avoiding issues like race conditions and deadlocks. In interviews, Multithreading plays a crucial role so let us understand some of the programming questions on Multithreading.
- What is a thread in Java?
A thread is the smallest unit of execution in a process. In Java, a thread is an independent path of execution that allows multiple operations to be performed concurrently.
2. How can you create a thread in Java?
There are two ways to create a thread in Java:
- Extending
Thread
class
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running...");
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}
2. Implementing Runnable
interface
class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread is running...");
}
}
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
}
}
3. What is the difference between start()
and run()
methods?
start()
: Creates a new thread and callsrun()
method in a separate thread.run()
: Executes in the main thread if directly called instead of a separate thread.
class MyThread extends Thread {
public void run() {
System.out.println("Run method executed by: " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.run(); // Runs in main thread (wrong way)
t1.start(); // Runs in a new thread (correct way)
}
}
4. What is a deadlock? How do you prevent it?
Deadlock occurs when two or more threads hold locks that the other needs, causing them to wait indefinitely.
This can be prevented by following strategies like:
Locking resources in a consistent order.
Using
tryLock()
with a timeout.Using higher-level concurrency utilities like
ExecutorService
orCountDownLatch
.
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 both locks.");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
synchronized (lock1) {
System.out.println("Thread 2 acquired both locks.");
}
}
});
t1.start();
t2.start();
}
}
5. Create a simple program that simulates a deadlock situation. The program should have two threads that try to acquire two locks in a different order, leading to a deadlock.
class A {
synchronized void methodA(B b) {
System.out.println("Thread 1: Holding lock A...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
b.last();
}
synchronized void last() {
System.out.println("Inside A's last method");
}
}
class B {
synchronized void methodB(A a) {
System.out.println("Thread 2: Holding lock B...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
a.last();
}
synchronized void last() {
System.out.println("Inside B's last method");
}
}
6. Print Even and Odd Numbers Using Two Threads
Write a program to print even and odd numbers from 1 to 100 using two threads. One thread should print even numbers, and the other should print odd numbers.
public class EvenOddThreadSafe {
public static void main(String[] args) {
EvenThread even = new EvenThread();
OddThread odd = new OddThread();
even.start();
odd.start();
try {
even.join();
odd.join();
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
class EvenThreadSafe extends Thread {
private static final Object lock = new Object(); // Lock object to synchronize
public void run() {
for (int i = 2; i <= 100; i += 2) {
synchronized (lock) {
System.out.println("Even : " + i);
}
}
}
}
class OddThreadSafe extends Thread {
private static final Object lock = new Object(); // Lock object to synchronize
public void run() {
for (int i = 1; i < 100; i += 2) {
synchronized (lock) {
System.out.println(("Odd : " + i));
}
}
}
}
/*
Why are we using join in this program ?
The join() method is used in this program to ensure that
the main thread waits for both the even and odd threads
to complete before it exits.
Without join(), the main thread might finish executing and exit
before the even and odd threads are done printing their respective numbers.
In that case, the program may exit while the threads are still running,
causing the program to terminate before the output is complete.
*/
7. Producer-Consumer Problem
The Producer-Consumer problem is a classic example of a multi-threaded problem where two threads (producer and consumer) share a common resource, typically a buffer or queue.
The producer thread generates data (produces), while the consumer thread consumes that data. The main challenge is to synchronize the producer and consumer threads to ensure the data is produced and consumed safely and efficiently, without race conditions.
import java.util.LinkedList;
import java.util.Queue;
class Sharedqueue {
private Queue<Integer> queue = new LinkedList<>();
private final int MAX_CAPACITY = 5; // Max queue size
// Producer method to add items to the queue
public synchronized void produce(int item) throws InterruptedException {
while (queue.size() == MAX_CAPACITY) {
wait(); // Wait if queue is full
}
queue.offer(item); // Add item to the queue
System.out.println("Produced: " + item);
notify(); // Notify the consumer that there is data to consume
}
// Consumer method to consume items from the queue
public synchronized int consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // Wait if queue is empty
}
int item = queue.poll(); // Remove item from the queue
System.out.println("Consumed: " + item);
notify(); // Notify the producer that there is space to produce more items
return item;
}
}
class Producer implements Runnable {
private Sharedqueue queue;
public Producer(Sharedqueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
int item = 1;
while (true) {
queue.produce(item++);
Thread.sleep(1000); // Simulate time taken to produce an item
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private Sharedqueue queue;
public Consumer(Sharedqueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
queue.consume();
Thread.sleep(1500); // Simulate time taken to consume an item
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
Sharedqueue queue = new Sharedqueue();
Thread producerThread = new Thread(new Producer(queue));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
}
}
8. Implement a Simple Thread Pool
Write a program that implements a simple thread pool (using ExecutorService
) to execute multiple tasks concurrently.
public class SimpleThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
System.out.println("Executing task: " + taskId + " in thread " + Thread.currentThread().getName());
});
}
executorService.shutdown();
}
}
9. Thread-safe Singleton Design Pattern
Problem: Implement the Singleton design pattern with thread safety. Ensure that only one instance of the class is created even when multiple threads are trying to access it.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// Private constructor to prevent instantiation
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
/*
Why are we using volatile keyword in singleton?
The volatile keyword in the Singleton class is used to ensure that
the instance of the Singleton class is properly visible across all threads.
when one thread creates the Singleton instance (inside the getInstance()
method), other threads might not see the updated value of instance immediately.
This is because, in Java, the value of a variable can be cached by
individual threads, and it might not be immediately visible to other threads,
leading to potential issues where multiple threads could create separate
instances of the Singleton.
The volatile keyword solves this problem by ensuring visibility of the
instance variable across all threads.
*/
10. Print Fibonacci Sequence Using Two Threads
Problem: Write a program to print the Fibonacci sequence up to a specified limit using two threads. One thread should print the Fibonacci numbers, and the other thread should print their indices.
class Fibonacci {
private int currentIndex = 0;
private int prev1 = 0, prev2 = 1;
// Synchronized method to print Fibonacci numbers
public synchronized void printFibonacci() {
// Calculate Fibonacci numbers and print them
while (currentIndex < 10) { // Limit to 10 for this example
int fibonacciNumber;
if (currentIndex == 0) {
fibonacciNumber = 0;
} else if (currentIndex == 1) {
fibonacciNumber = 1;
} else {
fibonacciNumber = prev1 + prev2;
prev1 = prev2;
prev2 = fibonacciNumber;
}
System.out.println("Fibonacci Number: " + fibonacciNumber);
currentIndex++;
notify(); // Notify the other thread to print the index
try {
if (currentIndex < 10) {
wait(); // Wait for the other thread to print the index
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Synchronized method to print the index of the Fibonacci number
public synchronized void printIndex() {
while (currentIndex < 10) { // Limit to 10 for this example
System.out.println("Index: " + currentIndex);
notify(); // Notify the other thread to print the Fibonacci number
try {
if (currentIndex < 10) {
wait(); // Wait for the other thread to print the Fibonacci number
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public class FibonacciThreads {
public static void main(String[] args) {
Fibonacci fibonacci = new Fibonacci();
// Thread 1: Prints Fibonacci numbers
Thread fibonacciThread = new Thread(() -> fibonacci.printFibonacci());
// Thread 2: Prints indices of Fibonacci numbers
Thread indexThread = new Thread(() -> fibonacci.printIndex());
// Start both threads
fibonacciThread.start();
indexThread.start();
// Wait for both threads to complete
try {
fibonacciThread.join();
indexThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
11. What is the difference between Thread.sleep()
and Object.wait()
?
Thread.sleep() pauses the current thread for a specified period without releasing the lock, whereas
Object.wait() releases the lock and puts the current thread in a waiting state until it is notified.
12. Explain the concept of thread safety and how you can ensure thread safety in Java.
Thread safety refers to ensuring that shared resources or variables are accessed by only one thread at a time, preventing data corruption.
Thread safety can be achieved by using synchronization (e.g., synchronized keyword), volatile variables, or using concurrent collections like ConcurrentHashMap.
13. What is the difference between wait()
, notify()
, and notifyAll()
in Java?
wait()
: Causes the current thread to release the lock and wait until it is notified by another thread.notify()
: Wakes up one thread that is waiting on the object's monitor.notifyAll()
: Wakes up all threads that are waiting on the object's monitor.
I hope you found this article insightful and valuable. Your support means a lot to me — feel free to share your thoughts, feedback, or suggestions in the comments.
If you enjoyed reading, don’t forget to clap, comment and share it with others who might benefit. Your encouragement inspires me to create more such content. Thank you