DEV Community

Cover image for 50 Interview Questions on Multithreading with Answers
Krishna Bhamare
Krishna Bhamare

Posted on

50 Interview Questions on Multithreading with Answers

Multithreading is an essential concept in Java software development, allowing multiple threads to execute concurrently, enhancing the performance of applications, especially in environments requiring real-time or intensive computational tasks. In interviews for roles like Java Developer, C++ Developer, Software Engineer, or even positions focused on performance optimization, multithreading often comes up.

Here are 50 interview questions on multithreading, along with their answers, to help you prepare:

  1. What is multithreading in Java?

    • Multithreading in Java allows multiple threads to run concurrently within a single program. This enables efficient CPU usage by allowing the operating system to execute different parts of a program at the same time. Multithreading improves the performance of applications, especially those that need to perform multiple tasks simultaneously, like games, real-time data processing, or web servers.
  2. How do you create a thread in Java?

    • You can create a thread in Java by either:
      • Extending the Thread class: Create a subclass of Thread and override the run() method to define the task.
      • Implementing the Runnable interface: Create a class that implements Runnable and define the run() method. Then, pass an instance of the class to a Thread object and start the thread by calling its start() method.
  3. What are the different states of a thread in Java?

    • The states are:
      • New: A thread is in this state when it is created but not yet started.
      • Runnable: A thread is in this state when it is ready to execute, but the JVM scheduler decides when to run it.
      • Blocked: A thread enters this state when it is waiting for a lock to be released by another thread.
      • Waiting: A thread enters this state when it is waiting indefinitely for another thread to perform a particular action (e.g., wait() method).
      • Timed Waiting: A thread enters this state when it is waiting for a specific amount of time (e.g., sleep(), join() with a timeout).
      • Terminated: A thread enters this state when it has finished execution or was terminated due to an error.
  4. Difference between Runnable and Thread in Java?

    • Runnable is an interface that defines a single method run(), which contains the code to be executed by a thread. Thread, on the other hand, is a class that represents an actual thread of execution. A Thread can implement the Runnable interface, allowing you to decouple the task from the thread management, enabling better code reuse.
  5. Purpose of the start() method in the Thread class?

    • The start() method is used to initiate the execution of a thread. Calling start() causes the thread to transition from the New state to the Runnable state. The actual execution of the thread happens when the JVM scheduler selects it to run. The start() method internally calls the run() method, where the thread’s task is defined.
  6. What is synchronization in Java?

    • Synchronization in Java is a mechanism that ensures that multiple threads do not access shared resources concurrently, which could lead to inconsistent states or race conditions. By using synchronization, only one thread can access a resource at a time, maintaining thread safety. This is especially important when multiple threads are modifying or reading shared data.
  7. How does synchronized keyword work in Java?

    • The synchronized keyword can be used to lock a method or a block of code, ensuring that only one thread can execute it at any given time. When a thread enters a synchronized method or block, it acquires the associated lock. Other threads that try to enter the same synchronized method or block must wait until the lock is released.
  8. What is a deadlock?

    • A deadlock is a situation where two or more threads are blocked forever, each waiting for a resource that the other holds. This can occur when threads are locked in a circular dependency, where thread A holds a lock needed by thread B, and thread B holds a lock needed by thread A. This prevents the threads from making progress.
  9. Different ways to achieve thread synchronization in Java?

    • Thread synchronization can be achieved using:
      • Synchronized Methods: Locking an entire method so that only one thread can execute it at a time.
      • Synchronized Blocks: Locking a specific block of code within a method, providing more granular control over which sections of code are synchronized.
      • Explicit Locks: Using ReentrantLock and other locks from the java.util.concurrent.locks package for more advanced synchronization features, such as trying to acquire a lock without blocking.
  10. Difference between synchronized method and synchronized block?

    • A synchronized method locks the entire method, ensuring that only one thread can execute that method at a time. This can potentially lock unnecessary parts of the code.
    • A synchronized block locks only the specific block of code within a method, providing more flexibility and allowing you to lock just the critical section that requires synchronization, reducing the scope of contention.
  11. How do threads communicate with each other?

    • Threads communicate using wait(), notify(), and notifyAll() methods, which are part of the Object class in Java. These methods are typically used for inter-thread communication in situations where threads need to coordinate their execution based on some shared state or condition.
  12. Purpose of wait(), notify(), and notifyAll()?

    • wait() causes the current thread to release the lock and enter a waiting state until another thread calls notify() or notifyAll() on the same object.
    • notify() wakes up a single thread that is waiting on the object's monitor.
    • notifyAll() wakes up all threads that are waiting on the object's monitor, allowing them to compete for the lock.
  13. Blocking queue in Java?

    • A blocking queue is a type of queue that supports operations where threads wait for space to become available when adding elements to the queue and wait for elements to become available when retrieving them. It is used in producer-consumer scenarios, where the producer waits if the queue is full and the consumer waits if the queue is empty.
  14. What is a Condition in Java concurrency?

    • A Condition provides a more advanced way to coordinate the execution of threads. It allows a thread to wait for a specific condition to be met before continuing. It is often used in conjunction with ReentrantLock and provides better control over thread communication compared to the wait() and notify() methods.
  15. What is thread safety and why is it important?

    • Thread safety is the property of an object or method that ensures it functions correctly when accessed concurrently by multiple threads. It is important because without thread safety, threads may interfere with each other, leading to inconsistent states, data corruption, or application crashes. Ensuring thread safety prevents these issues and allows multiple threads to interact with shared data safely.
  16. What are the ways to achieve thread safety in Java?

    • Thread safety can be achieved using:
      • Synchronization: Using synchronized blocks or methods to control access to shared resources.
      • Volatile Variables: Ensuring that changes to variables are immediately visible to other threads.
      • Atomic Variables: Using classes like AtomicInteger or AtomicReference to perform operations atomically.
      • Concurrent Collections: Using thread-safe collections like ConcurrentHashMap or CopyOnWriteArrayList for managing shared data.
  17. What is an atomic operation?

    • An atomic operation is a series of operations that are executed as a single, indivisible unit of work. It ensures that no other thread can interfere with the operation. For example, incrementing a variable atomically means that no other thread can change the value of the variable between the read and write operations.
  18. Classes in the java.util.concurrent.atomic package?

    • The java.util.concurrent.atomic package provides several classes designed for atomic operations on single variables:
      • AtomicInteger
      • AtomicLong
      • AtomicBoolean
      • AtomicReference These classes allow safe updates to variables in multithreaded environments without the need for explicit synchronization.
  19. What is the difference between volatile keyword and Atomic classes?

    • The volatile keyword ensures visibility of changes to a variable across all threads, meaning when one thread modifies a variable, other threads see the updated value immediately. However, it does not ensure atomicity (i.e., it doesn't prevent race conditions).
    • Atomic classes, such as AtomicInteger, provide both visibility and atomicity for operations like incrementing or updating a variable.
  20. What is the ExecutorService in Java and how is it different from using threads directly?

    • ExecutorService is an interface in Java that simplifies thread management. It provides methods to submit tasks for execution, manage thread pools, and shut down threads gracefully. Unlike using threads directly, ExecutorService abstracts away low-level thread management and provides better control over task scheduling and execution.
  21. How do you create an ExecutorService?

    • You can create an ExecutorService using the factory methods provided by the Executors class:
      • Executors.newFixedThreadPool(int nThreads) creates a fixed-size thread pool.
      • Executors.newCachedThreadPool() creates a thread pool that can expand as needed.
      • Executors.newSingleThreadExecutor() creates a single-threaded executor.
      • Executors.newScheduledThreadPool(int corePoolSize) creates a pool that supports scheduled tasks.
  22. Difference between execute() and submit() methods in ExecutorService.

    • execute(): Used for submitting tasks that do not return any result or need to be tracked (i.e., void tasks). It does not return any value.
    • submit(): Used for tasks that return a result. It returns a Future object, which can be used to retrieve the result of the task or check if it has completed.
  23. How do you gracefully shut down an ExecutorService?

    • You can gracefully shut down an ExecutorService by calling the shutdown() method. This initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. You can also use shutdownNow() to attempt to stop all actively executing tasks and halt the processing of waiting tasks.
  24. Difference between shutdown() and shutdownNow() methods in ExecutorService?

    • shutdown(): Initiates an orderly shutdown, where tasks that were already submitted are completed before the service is fully shut down. No new tasks will be accepted.
    • shutdownNow(): Tries to stop all actively executing tasks and attempts to stop any waiting tasks. It returns a list of the tasks that were waiting to be executed.
  25. What is a Future in Java and how is it related to ExecutorService?

    • A Future represents the result of an asynchronous computation. When you submit a task to an ExecutorService using submit(), it returns a Future object. You can use the Future object to check if the task is completed, retrieve the result, or cancel the task.
  26. How can you cancel a task that has been submitted to an ExecutorService?

    • You can cancel a task by calling the cancel() method on the Future object returned by the submit() method. If the task is still running, calling cancel() will attempt to interrupt it. If the task has already finished or been canceled, it will have no effect.
  27. What is a ScheduledExecutorService and how do you use it?

    • A ScheduledExecutorService is a type of ExecutorService that can schedule commands to run after a given delay or to execute periodically. It is useful for tasks that need to be repeated at fixed intervals or scheduled to run after a delay.
    • You can use methods like schedule() and scheduleAtFixedRate() to schedule tasks.
  28. How do you handle exceptions thrown by tasks submitted to an ExecutorService?

    • Exceptions thrown by tasks can be handled by catching them within the run() method of the task. If you are using submit(), you can handle exceptions by calling Future.get(). If the task throws an exception, get() will throw an ExecutionException, which can be caught and processed.
  29. Explain the lifecycle of an ExecutorService?

    • The lifecycle of an ExecutorService involves:
      • Creation: Instantiate an ExecutorService using one of the factory methods.
      • Task Submission: Submit tasks for execution using execute() or submit().
      • Execution: The service manages the execution of tasks using a thread pool.
      • Shutdown: The service is shut down gracefully using shutdown() or shutdownNow().
  30. What is a thread pool and why is it used?

    • A thread pool is a collection of pre-instantiated, reusable threads that are used to execute tasks. Thread pools are used to manage the execution of concurrent tasks efficiently, reducing the overhead of creating and destroying threads for each task. They improve performance by reusing threads and controlling the number of concurrent threads.
  31. Different types of thread pools provided by the Executors utility class?

    • The Executors utility class provides several types of thread pools:
      • Fixed Thread Pool: A pool with a fixed number of threads (newFixedThreadPool(int nThreads)).
      • Cached Thread Pool: A pool that creates new threads as needed but reuses previously constructed threads (newCachedThreadPool()).
      • Single Thread Executor: A pool with a single thread for executing tasks (newSingleThreadExecutor()).
      • Scheduled Thread Pool: A pool that supports scheduling tasks with a fixed rate or delay (newScheduledThreadPool(int corePoolSize)).
  32. How do you create a fixed thread pool in Java?

    • You can create a fixed thread pool by using the method Executors.newFixedThreadPool(int nThreads), where nThreads is the number of threads in the pool. This creates a pool with a fixed number of threads, and if all threads are busy, new tasks will wait until a thread becomes available.
  33. How do you create a cached thread pool in Java?

    • You can create a cached thread pool by using Executors.newCachedThreadPool(). This creates a pool that can dynamically adjust the number of threads, creating new ones as needed, and reusing previously idle threads when available.
  34. What is a single-thread executor?

    • A single-thread executor is an ExecutorService that uses a single worker thread to execute tasks. Tasks are executed sequentially in the order they are submitted, ensuring that only one task is running at any time.
  35. How do you create a scheduled thread pool?

    • You can create a scheduled thread pool by using Executors.newScheduledThreadPool(int corePoolSize). This creates a pool that can schedule tasks to run at fixed intervals or after a delay.
  36. What are the benefits of using a thread pool?

    • Benefits of using a thread pool include:
      • Efficient Resource Management: Reduces the overhead of creating and destroying threads for each task.
      • Improved Performance: Reuses threads, reducing the cost of thread creation.
      • Better Scalability: Controls the number of concurrent threads and balances system resources.
      • Task Queueing: Allows for efficient management of tasks in a queue, ensuring that they are executed in order.
  37. How does the thread pool manage the number of threads in the pool?

    • The thread pool manages the number of threads using the core pool size and maximum pool size. It creates new threads if the number of active threads is less than the core size, and if the number of active threads exceeds the core size, it creates additional threads up to the maximum pool size. Idle threads are terminated if they exceed the keep-alive time.
  38. Difference between a fixed thread pool and a cached thread pool?

    • A fixed thread pool has a fixed number of threads, and the size of the pool is determined at the time of creation. Once the number of threads is reached, new tasks are queued until a thread becomes available.
    • A cached thread pool creates new threads as needed and reuses threads that are no longer active. If there are no available threads, new ones are created dynamically, and idle threads are terminated after a certain period of inactivity.
  39. How does ThreadPoolExecutor work internally?

    • ThreadPoolExecutor internally maintains a pool of worker threads, which execute tasks submitted to it. It uses a blocking queue to hold tasks that are waiting for execution. The executor uses a core pool size to manage the number of threads, and can create additional threads up to the maximum pool size if necessary.
  40. What are the core and maximum pool sizes in ThreadPoolExecutor?

    • The core pool size is the minimum number of threads that are maintained in the pool, even if they are idle.
    • The maximum pool size is the maximum number of threads that the pool can have. If the number of active threads exceeds the core pool size, new threads can be created, but they cannot exceed the maximum pool size.
  41. What is the keep-alive time in ThreadPoolExecutor and how does it affect thread pool behavior?

    • The keep-alive time is the amount of time that an idle thread will remain alive before being terminated. If the number of threads in the pool exceeds the core pool size and the threads remain idle for the specified keep-alive time, they are terminated. This helps in managing resources by reducing unnecessary idle threads.
  42. Blocking queue and how is it used in ThreadPoolExecutor?

    • A blocking queue is used by ThreadPoolExecutor to hold tasks before they are executed. It ensures that tasks are executed in the order they are received and that threads are not overwhelmed by excessive task submissions. Examples of blocking queues include ArrayBlockingQueue, LinkedBlockingQueue, and SynchronousQueue.
  43. Different types of blocking queues you can use with ThreadPoolExecutor?

    • Some common types of blocking queues include:
      • ArrayBlockingQueue: A bounded blocking queue backed by an array.
      • LinkedBlockingQueue: A blocking queue backed by a linked node structure, with optional capacity limits.
      • SynchronousQueue: A queue that does not hold any elements; tasks must be transferred directly between threads.
      • PriorityBlockingQueue: A queue that orders elements based on their priority.
  44. How would you handle a scenario where you need to perform multiple tasks in parallel?

    • You can handle this by using a thread pool (e.g., ExecutorService) to submit multiple tasks concurrently. Each task will be executed by a separate thread from the pool, allowing tasks to run in parallel.
  45. Producer-consumer problem and how can you solve it in Java?

    • The producer-consumer problem involves two types of threads:
      • The producer, which generates data and puts it into a shared queue.
      • The consumer, which retrieves and processes the data from the queue.
    • This can be solved using a blocking queue, such as ArrayBlockingQueue or LinkedBlockingQueue. The producer thread can add items to the queue, and the consumer thread can remove items. The blocking queue handles synchronization, so the consumer waits if the queue is empty and the producer waits if the queue is full.
    • Another approach involves using synchronized methods or explicit locks to control access to shared resources between the producer and consumer.
  46. Implement a singleton class in a multithreaded environment.

    • A singleton class ensures that only one instance of the class is created, even in a multithreaded environment. In Java, this can be implemented using:

      1. Double-Checked Locking: This method uses synchronization with an additional check to ensure that the instance is created only once.
       public class Singleton {
           private static volatile Singleton instance;
      
           private Singleton() {}
      
           public static Singleton getInstance() {
               if (instance == null) {
                   synchronized (Singleton.class) {
                       if (instance == null) {
                           instance = new Singleton();
                       }
                   }
               }
               return instance;
           }
       }
      
      1. Bill Pugh Singleton Design: Using the enum type to ensure thread-safety and serialization.
       public enum Singleton {
           INSTANCE;
       }
      
  47. How do you handle exceptions in threads in Java?

    • Exceptions thrown within a thread can be handled using a try-catch block inside the run() method of the Runnable or Callable interface. If a thread encounters an exception, the exception is caught, and the thread can terminate gracefully.
    • Additionally, you can implement an UncaughtExceptionHandler for handling uncaught exceptions. This handler can be set for a thread to handle any exception that is not caught within the thread.
      thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
          public void uncaughtException(Thread t, Throwable e) {
              // Handle exception
          }
      });
    
  48. Daemon thread in Java.

    • A daemon thread is a thread that runs in the background and performs tasks such as garbage collection or other housekeeping operations. It does not prevent the JVM from exiting when all non-daemon threads have completed their execution.
    • Daemon threads are typically low-priority threads. They are automatically terminated when the JVM shuts down, so they should not be used for tasks that need to complete reliably.
    • To create a daemon thread, you can call the setDaemon(true) method before starting the thread:
      Thread thread = new Thread(() -> { /* Task */ });
      thread.setDaemon(true);
      thread.start();
    
  49. How do you create a daemon thread in Java?

    • To create a daemon thread in Java, you need to call the setDaemon(true) method on a Thread object before starting the thread. This marks the thread as a daemon thread, which will allow it to run in the background and be terminated automatically when the JVM exits.
      Thread daemonThread = new Thread(() -> {
          // Background task
      });
      daemonThread.setDaemon(true);  // Mark as daemon thread
      daemonThread.start();
    
  50. Benefits of using the ConcurrentHashMap?

    • ConcurrentHashMap is a thread-safe version of HashMap that allows multiple threads to read and write concurrently without locking the entire map.
    • The key benefits of using ConcurrentHashMap include:
      1. High Concurrency: Allows concurrent access by multiple threads with minimal contention. It achieves this by partitioning the map into segments, allowing threads to work on different segments simultaneously.
      2. Thread-Safety: Ensures that operations like put(), get(), and remove() are thread-safe without the need for external synchronization.
      3. Better Performance: Compared to synchronized HashMap or Hashtable, it provides better performance in concurrent environments.
      4. Non-blocking Reads: Threads can read data without locking the entire map, thus improving performance for read-heavy operations.

    Example of using ConcurrentHashMap:

    ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
    map.put(1, "one");
    map.put(2, "two");
    String value = map.get(1);  // Non-blocking read
    

Top comments (0)