
Here’s a structured list of multithreading interview questions, covering everything from basic to advanced levels.
Basic Multithreading Questions
1. What is multithreading in Java?
Multithreading in Java allows a program to execute multiple tasks simultaneously using threads. It improves performance by utilizing CPU cores efficiently and helps in creating responsive applications. For example, in a web server, each request can be handled by a separate thread instead of processing them sequentially. We can implement multithreading using Thread class or Runnable interface.
2. What is thread?
A thread is a lightweight process that runs independently within a program. It allows multiple tasks to execute concurrently, improving performance and responsiveness. In Java, we can create threads using the Thread
class or Runnable
interface
3. How is multithreading different from multiprocessing?
Multithreading lets us run multiple threads within the same process, sharing memory and resources. It’s lightweight and great for tasks like handling multiple user requests in a web server. Multiprocessing, on the other hand, runs multiple processes separately, each with its own memory. It’s heavier but avoids thread-safety issues, making it useful for CPU-intensive tasks like video rendering.
So, if we need efficient, shared execution, we go with multithreading. If we need true parallelism, multiprocessing is the way to go.
4. What are the benefits of multithreading?
Multithreading helps us make better use of CPU resources by allowing multiple tasks to run in parallel. This makes applications more responsive since background tasks don’t block the main execution. It also speeds processing large computations happens faster when tasks run simultaneously.
Plus, since threads share memory, it’s more efficient than creating separate processes. So, if we want speed, responsiveness, and efficient resource usage, multithreading is a great choice.
5. How do you create a thread in Java?
In Java, we create a thread in three ways by extending Thread
, implementing Runnable
, or using a lambda.
When we extend Thread
, we override the run()
method and start the thread using start()
. But since Java doesn’t support multiple inheritance, this approach limits flexibility.
Another one is implementing Runnable
, where we also override run()
, but instead of creating a thread directly, we pass its instance to a Thread
object. This allows us to extend other classes while keeping the code reusable.
The simplest one is using a lambda. Since Runnable
is a functional interface, we can pass the logic directly inside Thread
, reducing extra code. So, if we need flexibility, we go with Runnable
, and if we want cleaner code, we use a lambda.
6. What is the difference between extending Thread
and implementing Runnable
?
The key difference is that when we extend Thread
, our class directly inherits from it and overrides the run()
method. But since Java doesn’t support multiple inheritance, we can’t extend any other class.
On the other hand, when we implement Runnable
, we still override run()
, but instead of creating a thread directly, we pass an instance of our class to a Thread
object. This way, we keep our class more flexible and free to extend another class if needed.
So, if we don’t need inheritance, Thread
works fine, but for better flexibility and reusability, Runnable
is the better choice.
7. What are the states of a thread in Java?
A thread in Java goes through multiple states. It starts as NEW when created, then moves to RUNNABLE after calling start(), meaning it’s ready but waiting for CPU time.
Once scheduled, it enters RUNNING and executes its task. From there, it might get BLOCKED (waiting for a lock), WAITING (paused indefinitely), or TIMED WAITING (paused for a set time). After any of these, it always goes back to RUNNABLE state before running again. Finally, when the task is done, it reaches TERMINATED and stops
1 2 3 | NEW → RUNNABLE → RUNNING → TERMINATED ↑ ↓ (BLOCKED / WAITING / TIMED WAITING) |
8. How do you start and stop a thread?
To start a thread in Java, we create a Thread
object and call start()
, which begins its execution.
To stop a thread safely, we don’t use stop()
(since it’s deprecated). Instead, we generally use a flag variable. The thread keeps running while the flag is true
, and when we set it to false
, it exits run()
gracefully.
Another way is using interrupt()
. If the thread is sleeping or waiting, calling interrupt()
wakes it up by throwing InterruptedException
, allowing us to handle the stop inside catch
. We also check Thread.interrupted()
inside the loop to stop it cleanly.
9. What is the difference between start()
and run()
in threads?
Calling start()
creates a new thread and runs run()
inside it. This means the task executes in parallel with the main thread.
On the other hand, calling run()
directly doesn’t start a new thread. It just runs like a normal method call in the same thread. So, no concurrency happens.
10. What is the sleep()
method in Java, and how does it work?
The sleep()
method in Java pauses the current thread for a specified time. It’s a static method from Thread
class, meaning it always affects the thread that calls it.
While sleeping, the thread doesn’t release locks it holds – it just waits. After the time is up, it moves back to the runnable state, waiting for CPU time. If another thread calls interrupt()
while it’s sleeping, it throws InterruptedException
and wakes up early.
11. What is the volatile
keyword, and how is it different from synchronized
?
The volatile
keyword ensures that changes to a variable are immediately visible to all threads. It prevents CPU caching but doesn’t provide atomicity.
On the other hand, synchronized
ensures both visibility and atomicity by allowing only one thread to access the critical section at a time.
So, if multiple threads only read and write a variable without complex operations, volatile
is enough. But if we need to ensure atomic updates (like incrementing a counter), we use synchronized
.
12. When should one prefer volatile
over synchronized
?
We prefer volatile
when multiple threads only need to read a variable, and a single thread is writing to it. In this case, atomicity isn’t a concern because we’re sure that only one thread updates it. volatile
ensures visibility, so all threads always see the latest value immediately. Plus, it removes the overhead of synchronization, making it more efficient. For example, using volatile
with a signal variable.
But if we need both visibility and atomicity, like modifying shared data safely, we go with synchronized
, since it ensures only one thread can change the value at a time.
13. How can you make a thread wait for another thread to complete?
We can make a thread wait for another thread to complete using the join()
method. When we call thread.join()
, the current thread pauses and waits until the specified thread finishes execution.
For example, if Thread A
calls B.join()
, A
will wait until B
completes before continuing. We can also use join(time)
to wait for a specific duration instead of indefinitely.
14. What is a race condition? How do you handle it?
A race condition happens when multiple threads try to modify shared data at the same time, leading to inconsistent results. This usually occurs when thread execution order isn’t controlled, causing unexpected behavior.
To handle it, we use synchronization techniques like synchronized
blocks and methods, ReentrantLock
, or Atomic
variables. So, these ensure that only one thread modifies the shared resource at a time, preventing inconsistent data updates.
15. How does Java manage multiple threads on a multi-core processor?
Java relies on the OS thread scheduler to assign JVM threads to CPU cores, enabling true parallelism on multiple cores and context switching when threads exceed number of core count.
Suppose we have an octa-core CPU and create 8 threads, each thread will run truly parallel on separate cores. But, if you create more than 8 threads, the OS will perform context switching to make it appear as if all threads are running simultaneously.
16. Why must synchronization in Java use the same lock monitor instead of multiple locks for different threads? What issues arise if different locks are used?
We use the same lock monitor in Java synchronization to make sure only one thread can access the critical section at a time. If different locks were used, multiple threads could enter the synchronized block simultaneously, completely breaking synchronization. This would lead to race conditions and inconsistent data.
17. Why is using synchronized(this) not ideal when an instance has multiple critical sections?
If we use synchronized(this)
, we’re locking the entire object’s monitor. That means even if two threads are working on separate synchronized methods or blocks within the same instance, one has to wait for the other to release the lock. This reduces concurrency.
18. How does using separate lock objects improve concurrency in a multithreaded environment?
By using separate lock objects for different critical sections in the same instance, we let multiple threads work at the same time. This way, one thread using lockA
won’t block another using lockB
within that instance, helping things run faster and smoother.
19. Can exceptions in one thread propagate to another thread automatically?
No, exceptions in one thread don’t automatically propagate to another thread. Each thread runs independently, so if a thread throws an exception, it won’t affect other threads unless we explicitly handle it, like using shared data or mechanisms like ExecutorService.awaitTermination()
to check for failures.
20. Why do we need to handle InterruptedException inside the thread itself?
We need to handle InterruptedException
inside the thread because it’s a signal that the thread should stop or perform cleanup. If we ignore it, the thread might keep running when it was supposed to stop. Instead of ignoring the exception, we should either propagate it or gracefully exit by checking if the current thread is interrupted, like using Thread.currentThread().isInterrupted().
21. What are the core different methods available in the Thread lifecycle and their uses?
In Java, multiple methods are called during a thread’s lifecycle.
First, to start a thread, we use start()
, which moves it from NEW to RUNNABLE. If we mistakenly call run()
, it simply runs in the current thread instead of starting a new one.
To pause execution, sleep(ms)
temporarily puts the thread in TIMED_WAITING, while join()
makes the current thread wait until another thread finishes. Similarly, wait()
puts a thread on hold until another thread calls notify()
or notifyAll()
to resume execution. These are key for thread synchronization.
When stopping a thread, interrupt()
signals it to stop, but the thread must handle it properly. Finally, isAlive()
checks if a thread is still running or has finished execution.
22. What is the difference between wait(), sleep(), and yield()?
When a thread calls wait()
, it pauses execution, releases the lock, and stays in the waiting state until another thread calls notify()
.
sleep(ms)
, on the other hand, pauses execution for a specified time but retains the lock, preventing other threads from proceeding. yield()
simply hints to the scheduler that the thread is willing to give up CPU time, but it does not guarantee a context switch.
23. What is Context Switching?
Context switching is the process where the CPU saves the current state of a thread and switches to another thread for multitasking. It happens when the scheduler suspends a running thread and assigns CPU time to another, ensuring that multiple tasks make progress without one thread blocking others. This helps keep the CPU actively working on different tasks instead of idling while waiting for one thread to finish.
24. What is the difference between notify() and notifyAll()?
notify()
wakes up one waiting thread only, while notifyAll()
wakes up all waiting threads on the same monitor. If multiple threads are waiting, notify()
selects one at randomly, whereas notifyAll()
ensures all threads get a chance to compete for execution. So, we use notify()
when only one thread needs to proceed, and notifyAll()
when multiple threads should be considered.
For more details on Java’s concurrency features, you can also check out the official Java documentation on multithreading.
Leave a Reply