As a software developer, proficiency in Java multi-threading is a must. If you are a fresher, preparing with our Java multi-threading interview questions can significantly boost your confidence and improve your chances of landing your dream job. This guide covers the top 25+ Java multi-threading interview questions and answers, from the basics of thread creation to mastering advanced concepts like synchronization and the Java Memory Model.
26 Java Multi Threading Interview Questions and Answers
- What is multithreading in Java?
- How do you create a thread in Java?
- What is the difference between start() and run() methods in Java threads?
- What are the different states in a thread’s lifecycle?
- What is thread synchronization, and why is it important?
- How does the synchronized keyword work in Java?
- What is a deadlock in multithreading, and how can it be avoided?
- What is the difference between wait() and sleep() methods in Java?
- What is a daemon thread in Java?
- What is the purpose of the volatile keyword in Java?
- What is the difference between synchronized and volatile in Java?
- What is a thread pool, and why is it used?
- What is the Callable interface in Java?
- What is the difference between Runnable and Callable in Java?
- What is the ExecutorService in Java?
- What is a Future in Java?
- What is the difference between notify() and notifyAll() in Java?
- What is a race condition in multithreading, and how can it be prevented?
- What is the ReentrantLock in Java, and how does it differ from the synchronized keyword?
- What is the ThreadLocal class in Java, and how is it used?
- What is the Java Memory Model (JMM), and why is it important?
- What are atomic operations in Java, and how do they help in multithreading?
- What is the difference between invokeAll() and invokeAny() in the ExecutorService?
- How does the ForkJoinPool work in Java?
- What is a Semaphore in Java, and how is it used?
- What is the CountDownLatch in Java?
1. What is multithreading in Java?
Multithreading in Java is a feature that allows concurrent execution of two or more threads. Each thread runs parallel to others, enabling efficient utilization of CPU resources. This is particularly useful for performing multiple tasks simultaneously within a single program, such as handling user interactions while processing background computations.
2. How do you create a thread in Java?
In Java, threads can be created in two primary ways:
Extending the Thread
class:
class MyThread extends Thread {
public void run() {
// Task to execute
}
}
MyThread thread = new MyThread();
thread.start();
Implementing the Runnable
interface:
class MyRunnable implements Runnable {
public void run() {
// Task to execute
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
Implementing Runnable
is often preferred as it allows the class to extend other classes, promoting better object-oriented design.
3. What is the difference between start()
and run()
methods in Java threads?
start()
method: Initiates a new thread of execution by calling therun()
method internally. It ensures that the new thread runs concurrently with the existing threads.run()
method: Contains the code that constitutes the new thread’s task. Callingrun()
directly does not create a new thread; it executes the method in the current thread’s context.
Therefore, to achieve multithreading, start()
should be used instead of calling run()
directly.
4. What are the different states in a thread’s lifecycle?
A thread in Java can exist in one of the following states:
- New: The thread is created but not yet started.
- Runnable: The thread is ready to run and is waiting for CPU allocation.
- Blocked: The thread is waiting for a monitor lock to enter a synchronized block/method.
- Waiting: The thread is waiting indefinitely for another thread to perform a particular action.
- Timed Waiting: The thread is waiting for another thread to perform an action for a specified waiting time.
- Terminated: The thread has completed its execution.
Understanding these states helps in managing thread behavior during execution.
5. What is thread synchronization, and why is it important?
Thread synchronization in Java is a mechanism to control the access of multiple threads to shared resources. It ensures that only one thread can access the resource at a time, preventing data inconsistency and race conditions. Synchronization is crucial when threads share mutable data to maintain data integrity and avoid unpredictable behavior.
6. How does the synchronized
keyword work in Java?
The synchronized
keyword in Java can be applied to methods or blocks to control access to critical sections of code:
Synchronized Method:
public synchronized void synchronizedMethod() {
// synchronized code
}
This ensures that only one thread can execute this method on the same object instance at a time.
Synchronized Block:
public void method() {
synchronized(this) {
// synchronized code
}
}
This allows more granular control by synchronizing only a portion of the method, potentially improving performance by reducing the scope of synchronization.
7. What is a deadlock in multithreading, and how can it be avoided?
A deadlock occurs when two or more threads are blocked forever, each waiting for the other to release a resource. This situation leads to a standstill where none of the threads can proceed.
Avoiding Deadlocks:
- Resource Ordering: Ensure that all threads acquire resources in a consistent order.
- Timeouts: Use timeouts when attempting to acquire locks.
- Deadlock Detection: Implement algorithms to detect and recover from deadlocks.
- Avoid Nested Locks: Minimize the use of nested locks to reduce the risk of deadlocks.
By following these strategies, the occurrence of deadlocks can be minimized.
8. What is the difference between wait()
and sleep()
methods in Java?
wait()
method: Belongs to theObject
class; it causes the current thread to release the monitor and wait until another thread invokesnotify()
ornotifyAll()
on the same object. It must be called within a synchronized context.sleep()
method: Belongs to theThread
class; it pauses the execution of the current thread for a specified period without releasing the lock. It can be called from any context.
Understanding the distinction is essential for proper thread management and avoiding synchronization issues.
9. What is a daemon thread in Java?
A daemon thread in Java is a background thread that provides services to user threads. The Java Virtual Machine (JVM) terminates daemon threads automatically when all user threads have finished their execution. Daemon threads are typically used for tasks like garbage collection and background monitoring.
Creating a Daemon Thread:
Thread daemonThread = new Thread(() -> {
// daemon task
});
daemonThread.setDaemon(true);
daemonThread.start();
It’s important to set a thread as a daemon before starting it; otherwise, an IllegalThreadStateException
will be thrown.
10. What is the purpose of the volatile
keyword in Java?
The volatile
keyword in Java is used to indicate that a variable’s value may be changed by different threads. Declaring a variable as volatile
ensures that its value is always read from and written to the main memory, not from a thread’s local cache. This guarantees visibility of changes across threads, providing a lightweight synchronization mechanism.
Example:
private volatile boolean flag = true;
In this example, any thread that reads the flag
variable will see the most recent write by any other thread.
Key Points:
volatile
ensures visibility of changes to variables across threads.- It does not, however, guarantee atomicity for compound actions.
- It’s suitable for flags or state variables that are read and written by multiple threads.
- It cannot be used to make a class thread-safe when multiple threads update a shared mutable variable.
11. What is the difference between synchronized
and volatile
in Java?
volatile
: Ensures visibility of changes to variables across threads but does not provide atomicity or mutual exclusion. It’s suitable for variables that are read and written by multiple threads without involving compound actions.synchronized
: Provides both mutual exclusion and visibility. It ensures that only one thread can execute a block of code at a time, making it suitable for protecting critical sections where compound actions are performed on shared variables.
Example:
// Using synchronized
public synchronized void increment() {
count++;
}
In this example, the increment
method is synchronized to ensure that the count
variable is updated atomically.
12. What is a thread pool, and why is it used?
A thread pool is a collection of pre-instantiated reusable threads. Instead of creating new threads for every task, which can be resource-intensive, tasks are submitted to the thread pool, and one of the available threads executes the task.
Benefits:
- Improved Performance: Reduces the overhead of thread creation and destruction.
- Resource Management: Controls the number of concurrent threads, preventing resource exhaustion.
- Task Management: Simplifies the management of task execution, scheduling, and load balancing.
Example:
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
// Task implementation
});
executor.shutdown();
In this example, a fixed thread pool with 10 threads is created, and a task is submitted for execution.
13. What is the Callable
interface in Java?
The Callable
interface, introduced in Java 5, is similar to Runnable
but can return a result and throw a checked exception. It represents a task that can be executed concurrently and is part of the java.util.concurrent
package.
Example:
Callable<Integer> task = () -> {
// Perform computation
return 42;
};
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(task);
Integer result = future.get(); // Retrieves the result
executor.shutdown();
In this example, a Callable
task is submitted to an executor service, and the result is retrieved using a Future
object.
14. What is the difference between Runnable
and Callable
in Java?
Runnable
: Does not return a result and cannot throw checked exceptions. It’s suitable for tasks that do not require a result or error handling.Callable
: Returns a result and can throw checked exceptions. It’s suitable for tasks that require a result or need to handle exceptions.
Example:
// Runnable example
Runnable task = () -> {
// Task implementation
};
// Callable example
Callable<Integer> task = () -> {
// Task implementation
return 42;
};
In these examples, the Runnable
task performs an action without returning a result, while the Callable
task returns a result.
15. What is the ExecutorService
in Java?
ExecutorService
is an interface in the java.util.concurrent
package that provides methods for managing termination and methods that can produce a Future
for tracking progress of one or more asynchronous tasks. It is a higher-level replacement for working with threads directly.
Example:
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
// Task implementation
});
executor.shutdown();
In this example, an ExecutorService
is used to manage a pool of threads for executing tasks.
16. What is a Future
in Java?
A Future
represents the result of an asynchronous computation. It provides methods to check if the computation is complete, to wait for its completion, and to retrieve the result. The result can only be retrieved when the computation has completed; if the computation has not completed, the get
method will block until it is complete.
Example:
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<Integer> future = executor.submit(() -> {
// Perform computation
return 42;
});
if (future.isDone()) {
Integer result = future.get();
}
executor.shutdown();
In this example, a Future
is used to retrieve the result of an asynchronous computation.
17. What is the difference between notify()
and notifyAll()
in Java?
Example:
In Java, both notify()
and notifyAll()
are methods used for inter-thread communication within synchronized blocks or methods. They are called on an object to wake up threads that are waiting on that object’s monitor.
notify()
: Wakes up a single thread that is waiting on the object’s monitor. If multiple threads are waiting, the choice of which thread to wake up is non-deterministic and depends on the thread scheduler. The awakened thread cannot proceed until the current thread relinquishes the lock on the object.- Usage Scenario: Suitable when only one thread needs to proceed, and you are confident that waking up a single thread will not cause any issues. For example, in a producer-consumer scenario where only one consumer should consume the produced item.
notifyAll()
: Wakes up all threads that are waiting on the object’s monitor. The highest priority thread will run first in most situations, though this is not guaranteed. Each awakened thread will compete to acquire the lock; only one will succeed and proceed, while others will continue waiting.- Usage Scenario: Useful when multiple threads are waiting for a condition to be met, and any or all of them can proceed once the condition changes. This approach reduces the risk of thread starvation and ensures that all waiting threads have an opportunity to proceed.
Example:
synchronized (lock) {
while (conditionNotMet()) {
lock.wait();
}
// Perform actions when the condition is met
lock.notify(); // Wakes up one waiting thread
// or
lock.notifyAll(); // Wakes up all waiting threads
}
Key Differences:
- Number of Threads Notified:
notify()
wakes up a single waiting thread, whereasnotifyAll()
wakes up all waiting threads. - Risk of Missed Notifications: Using
notify()
can lead to situations where some threads remain perpetually waiting if the notified thread doesn’t handle the condition appropriately.notifyAll()
reduces this risk by ensuring all waiting threads are notified. - Performance Considerations:
notify()
may be more efficient when only one thread needs to proceed, as it reduces the overhead of waking up multiple threads. However,notifyAll()
is safer in scenarios where multiple threads are waiting for the same condition, as it prevents potential deadlocks.
It’s essential to choose between notify()
and notifyAll()
based on the specific requirements of your application to ensure correct and efficient inter-thread communication.
18. What is a race condition in multithreading, and how can it be prevented?
A race condition occurs in a multithreaded environment when two or more threads access shared data concurrently, and the final outcome depends on the sequence of thread execution. This can lead to inconsistent or incorrect results, as the threads “race” to access or modify the shared resource.
Example:
public class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
If multiple threads call the increment()
method simultaneously, the lack of synchronization can lead to a race condition, resulting in an incorrect count
value.
Prevention Strategies:
Synchronization: Use the synchronized
keyword to control access to critical sections of code, ensuring that only one thread can execute the synchronized block at a time.
Example:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Locks: Utilize explicit lock objects from the java.util.concurrent.locks
package to manage access to shared resources.
Example:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
Atomic Variables: Use classes from the java.util.concurrent.atomic
package, such as AtomicInteger
, which provide atomic operations for variables, eliminating the need for explicit synchronization.
Example:
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
By implementing these strategies, you can prevent race conditions and ensure that shared resources are accessed in a thread-safe manner, maintaining data consistency and integrity.
19. What is the ReentrantLock
in Java, and how does it differ from the synchronized
keyword?
ReentrantLock
is a class in the java.util.concurrent.locks
package that provides explicit locking mechanisms, offering greater flexibility compared to the implicit locks provided by the synchronized
keyword.
Key Differences:
Explicit vs. Implicit Locking: synchronized
provides implicit locking; the lock is acquired and released automatically. In contrast, ReentrantLock
requires explicit acquisition and release of the lock using lock()
and unlock()
methods.
Example with ReentrantLock
:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
Fairness: ReentrantLock
can be configured for fairness by passing true
to its constructor, ensuring that the longest-waiting thread acquires the lock next. The synchronized
keyword does not offer this capability.
Example:
Lock fairLock = new ReentrantLock(true);
Interruptibility: ReentrantLock
provides the lockInterruptibly()
method, allowing a thread to be interrupted while waiting for the lock. This feature is not available with the synchronized
keyword.
Try Locking: With ReentrantLock
, you can attempt to acquire the lock without blocking indefinitely using the tryLock()
method, optionally with a timeout. This is not possible with synchronized
.
Example:
if (lock.tryLock()) {
try {
// Access shared resource
} finally {
lock.unlock();
}
} else {
// Perform alternative actions
}
Condition Variables: ReentrantLock
allows the creation of multiple Condition
objects for more complex thread synchronization scenarios, whereas synchronized
uses a single implicit monitor per object.
Example:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
When to Use ReentrantLock
:
- When you require advanced synchronization features such as fairness, interruptibility, or multiple condition variables.
- In scenarios where you need to attempt to acquire a lock without blocking indefinitely.
- When you need to implement a more complex locking mechanism that
synchronized
cannot provide.
When to Use synchronized
:
- For simple synchronization needs where the additional features of
ReentrantLock
are unnecessary. - When you prefer less verbose and more straightforward code, as
synchronized
handles lock acquisition and release automatically.
It’s essential to choose the appropriate locking mechanism based on the specific requirements of your application to ensure correct and efficient thread synchronization.
20. What is the ThreadLocal
class in Java, and how is it used?
The ThreadLocal
class in Java provides thread-local variables. Each thread accessing such a variable has its own, independently initialized copy of the variable. ThreadLocal
is particularly useful when you want to avoid shared state between threads but still need to maintain state within a single thread.
Usage:
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);
public int getValue() {
return threadLocalValue.get();
}
public void setValue(int value) {
threadLocalValue.set(value);
}
}
Key Points:
- Isolation: Each thread has its own isolated instance of the variable, preventing unintended interference between threads.
- Initialization: You can provide an initial value for the thread-local variable using the
withInitial()
method. - Access: Use the
get()
method to retrieve the current thread’s value and theset()
method to modify it. - Use Cases: Commonly used in scenarios like user sessions, transaction contexts, or any situation where you need to maintain per-thread state without synchronization.
Example:
public class UserService {
private static final ThreadLocal<String> currentUser = ThreadLocal.withInitial(() -> null);
public void login(String user) {
currentUser.set(user);
}
public void logout() {
currentUser.remove();
}
public String getCurrentUser() {
return currentUser.get();
}
}
In this example, each thread can independently log in and log out a user without affecting other threads, thanks to the thread-local storage of the currentUser
variable.
Important Consideration:
- Memory Management: It’s crucial to remove the thread-local variable when it’s no longer needed by calling the
remove()
method to prevent potential memory leaks, especially in environments with thread pooling.
By using ThreadLocal
, you can achieve thread confinement, ensuring that variables are not shared between threads, which simplifies development by reducing the need for synchronization.
21. What is the Java Memory Model (JMM), and why is it important?
The Java Memory Model defines how threads interact through memory and what behaviors are allowed in concurrent execution. It specifies rules about visibility of changes to variables across threads and the ordering of operations, ensuring predictable and consistent behavior in multithreaded programs.
Key Aspects:
- Visibility: Determines when changes made by one thread become visible to others.
- Atomicity: Ensures that operations are performed as a single, indivisible step.
- Ordering: Defines the sequence in which operations occur, preventing issues like instruction reordering.
Understanding the JMM is crucial for writing thread-safe code without unintended side effects.
22. What are atomic operations in Java, and how do they help in multithreading?
Atomic operations are actions that are performed entirely or not at all, without any intermediate states visible to other threads. In Java, classes in the java.util.concurrent.atomic
package, such as AtomicInteger
and AtomicReference
, provide methods for atomic operations, eliminating the need for explicit synchronization.
Example:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
Using atomic classes ensures thread safety for simple operations, improving performance by reducing synchronization overhead.
23. What is the difference between invokeAll()
and invokeAny()
in the ExecutorService?
Both methods are used to execute a collection of tasks in an ExecutorService
, but they behave differently:
invokeAll()
: Executes all tasks and returns a list of Future
objects representing their results. It waits for all tasks to complete before returning.
Example:
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<Integer>> tasks = Arrays.asList(
() -> 1,
() -> 2,
() -> 3
);
List<Future<Integer>> results = executor.invokeAll(tasks);
executor.shutdown();
invokeAny()
: Executes the tasks and returns the result of one successfully completed task (the first to complete without throwing an exception). It cancels the remaining tasks.
Example:
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<Integer>> tasks = Arrays.asList(
() -> 1,
() -> 2,
() -> 3
);
Integer result = executor.invokeAny(tasks);
executor.shutdown();
Choosing between these methods depends on whether you need all task results or just the first successful one.
24. How does the ForkJoinPool
work in Java?
ForkJoinPool
is a specialized implementation of ExecutorService
designed for work-stealing algorithms, efficiently handling tasks that can be broken into smaller subtasks recursively. It is particularly suited for parallel processing and divide-and-conquer algorithms.
Key Concepts:
- Forking: Splitting a task into smaller subtasks.
- Joining: Combining the results of subtasks to produce the final outcome.
Example:
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;
public class SumTask extends RecursiveTask<Integer> {
private int[] array;
private int start, end;
public SumTask(int[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if (end - start <= 10) { // Threshold for splitting tasks
int sum = 0;
for (int i = start; i < end; i++) {
sum += array[i];
}
return sum;
} else {
int mid = (start + end) / 2;
SumTask leftTask = new SumTask(array, start, mid);
SumTask rightTask = new SumTask(array, mid, end);
leftTask.fork();
int rightResult = rightTask.compute();
int leftResult = leftTask.join();
return leftResult + rightResult;
}
}
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool();
int[] array = new int[100];
// Initialize array with values
SumTask task = new SumTask(array, 0, array.length);
int result = pool.invoke(task);
System.out.println("Sum: " + result);
}
}
ForkJoinPool
enhances performance by utilizing all available processors and balancing the load among threads.
25. What is a Semaphore
in Java, and how is it used?
A Semaphore
is a synchronization aid that controls access to a shared resource through permits. It can restrict the number of threads that access certain resources concurrently.
Example:
import java.util.concurrent.Semaphore;
public class ResourceAccess {
private Semaphore semaphore = new Semaphore(3); // Allow 3 threads to access
public void accessResource() {
try {
semaphore.acquire();
// Access shared resource
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
}
In this example, only three threads can access the resource simultaneously, preventing overuse and potential contention.
26. What is the CountDownLatch
in Java?
CountDownLatch
is a synchronization aid that allows one or more threads to wait until a set of operations being performed by other threads completes. It uses a counter that is decremented by calls to the countDown()
method and causes threads to wait by calling the await()
method until the counter reaches zero.
Example:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) {
int numberOfThreads = 3;
CountDownLatch latch = new CountDownLatch(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
new Thread(new Worker(latch)).start();
}
try {
latch.await(); // Main thread waits until the count reaches zero
System.out.println("All threads have finished execution. Proceeding with main thread.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("Main thread interrupted.");
}
}
}
class Worker implements Runnable {
private final CountDownLatch latch;
Worker(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
// Simulate task execution
System.out.println(Thread.currentThread().getName() + " is executing.");
Thread.sleep(1000); // Simulate work by sleeping
System.out.println(Thread.currentThread().getName() + " has finished execution.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println(Thread.currentThread().getName() + " was interrupted.");
} finally {
latch.countDown(); // Decrement the count of the latch
}
}
}
Use Cases:
- Starting a Service After Initialization: Ensuring that a service starts only after all required initialization tasks are completed by multiple threads.
- Parallel Task Execution: Waiting for multiple parallel tasks to finish before proceeding with the next step in a process.
- Testing: Coordinating the execution of multiple threads in unit tests to ensure consistent test results.
CountDownLatch
is a versatile tool in concurrent programming, providing a simple yet effective way to coordinate multiple threads and ensure that certain operations are completed before others proceed.
Learn More: Carrer Guidance | Hiring Now!
Top 50 Tosca Real-Time Scenario Questions and Answers
Advanced TOSCA Test Automation Engineer Interview Questions and Answers with 5+ Years of Experience
Tosca Interview Questions for Freshers with detailed Answers
Tosca interview question and answers- Basic to Advanced
JCL Interview Questions and Answers
TestNG Interview Questions and Answers