Top Java Concurrency Interview Questions

    1

    What is the difference between concurrency and parallelism?

    Concurrency is about dealing with multiple tasks at once. It's a design principle where tasks can be in progress at the same time, managed by interleaving their execution on a single CPU core. It's about structuring a program to handle multiple workflows.

    Parallelism is about doing multiple tasks at once. It's an execution model where tasks run simultaneously on multiple CPU cores. It's about improving performance by running things in parallel.

    FeatureConcurrencyParallelism
    NatureA way to structure a program.A way to execute a program.
    CPU CoresCan be achieved with a single CPU core.Requires multiple CPU cores.
    GoalTo improve responsiveness and handle multiple events.To improve performance and throughput.
    ExampleA web server handling multiple client requests by switching between them.A video editor rendering multiple frames on different cores simultaneously.
    Concurrency Parallelism CPU Core Time → A B A B A B CPU Core 1 Time → A A CPU Core 2 Time → B B Task A Task B
    Concurrency: Interleaves multiple tasks (A and B) on a single core — both are in progress, but only one runs at a time.
    Parallelism: Runs tasks A and B simultaneously on separate cores — both truly run at the same time.
    2

    What is a context switch?

    A context switch is the process of storing the state of a thread or process so that it can be restored and resume execution at a later point. This allows multiple processes or threads to share a single CPU. Context switching is computationally expensive as it involves saving and loading registers, memory maps, and updating various kernel data structures. But it is necessary to achieve concurrency.

    3

    What are the main challenges in concurrent programming?

    • Race Conditions: When the result of a computation depends on the non-deterministic scheduling of two or more threads.
    • Deadlocks: When two or more threads are blocked forever, each waiting for a resource held by another.
    • Livelocks: When threads are continuously active but unable to make progress.
    • Starvation: When a thread is perpetually denied access to necessary resources.
    • Data Races: A type of race condition where one thread writes to a memory location while another thread reads from it, without proper synchronization.
    4

    What are user threads and daemon threads?

    • User Threads: High-priority threads that are critical to the application. The Java Virtual Machine (JVM) will wait for all user threads to complete their execution before exiting.
    • Daemon Threads: Low-priority threads that provide background services to user threads (e.g., garbage collection). The JVM will exit as soon as all user threads have finished, even if daemon threads are still running.

    You can set a thread as a daemon thread by calling thread.setDaemon(true) before it is started.

    5

    What is multitasking and how does it differ from multithreading?

    Multitasking is an operating system feature that allows multiple tasks or processes to run simultaneously, giving the illusion of parallel execution. The OS divides system resources among these tasks and switches between them rapidly.

    Types of Multitasking:

    1. Process-based Multitasking: Multiple independent processes run simultaneously (e.g., running a Java IDE and a text editor at the same time)
    2. Thread-based Multitasking: Multiple threads within the same process execute concurrently (e.g., JUnit running test cases in parallel)

    Key Differences:

    • Multitasking involves multiple processes with separate memory spaces
    • Multithreading involves multiple threads sharing the same memory space within a process
    • Thread-based multitasking is more efficient due to shared resources and lower context switching overhead
    6

    What is the main thread in Java?

    Every Java program has at least one thread called the main thread, which is created automatically by the JVM when the program starts. The main thread is responsible for executing the main() method and is a non-daemon thread.

    public class MainThreadExample { public static void main(String[] args) { System.out.println("Current thread: " + Thread.currentThread().getName()); System.out.println("Is daemon: " + Thread.currentThread().isDaemon()); } }

    Output:

    Current thread: main
    Is daemon: false
    
    7

    How can you run multiple threads in a Java program?

    You can run multiple threads in Java by creating instances of the Thread class or implementing the Runnable interface and starting them with the start() method. When you call start(), each thread runs concurrently.

    Example 1: Extending the Thread class

    class MyThread extends Thread { @Override public void run() { System.out.println("Thread: " + Thread.currentThread().getName()); } } public class Main { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); // Starts thread t1 t2.start(); // Starts thread t2 } }

    Example 2: Implementing Runnable

    class MyRunnable implements Runnable { @Override public void run() { System.out.println("Thread: " + Thread.currentThread().getName()); } } public class Main { public static void main(String[] args) { Thread t1 = new Thread(new MyRunnable()); Thread t2 = new Thread(new MyRunnable()); t1.start(); // Starts thread t1 t2.start(); // Starts thread t2 } }

    You can also use lambda expressions for simple tasks:

    Thread t1 = new Thread(() -> System.out.println("Lambda Thread 1")); Thread t2 = new Thread(() -> System.out.println("Lambda Thread 2")); t1.start(); t2.start();

    Each call to start() launches a new thread that executes its run() method independently.

    8

    Can you overload the run() method? What happens if you don't override it?

    Yes, you can overload the run() method, but it's considered bad practice because:

    1. The start() method only calls the no-argument run() method
    2. Overloaded run() methods won't be executed by the thread
    3. It can lead to confusion and unexpected behavior
    class MyThread extends Thread { // This will be called by start() @Override public void run() { System.out.println("Thread running"); } // This overloaded method will NOT be called by start() public void run(String message) { System.out.println("Overloaded run: " + message); } }

    If you don't override run():

    • The default run() method from the Thread class will be executed
    • This method has an empty implementation, so nothing will happen
    • The thread will start and immediately terminate without doing any work
    9

    Can you override the start() method?

    You can override start(), but it's not recommended because:

    1. No new thread will be created
    2. The run() method won't be called automatically
    3. You lose all the thread initialization functionality provided by the Thread class
    class MyThread extends Thread { @Override public void start() { // This will NOT create a new thread System.out.println("Custom start method"); // run() won't be called automatically } @Override public void run() { System.out.println("Thread running"); } }

    Best Practice: Always call super.start() if you must override start() to maintain proper thread behavior.

    10

    What are the different ways to identify a process?

    A process is any program in a working state that has its own memory space and resources. You can identify processes using:

    1. Process ID (PID): Unique identifier assigned by the operating system
    2. Process Name: The executable name (e.g., "chrome.exe", "java.exe")
    3. Memory Usage: Amount of RAM consumed by the process
    4. CPU Usage: Percentage of CPU time used by the process

    Tools to view processes:

    • Windows: Task Manager, Process Explorer
    • Linux/Unix: ps, top, htop commands
    • Java: jps command for Java processes
    11

    How can you see threads within a process?

    Threads are lightweight units within a process. To view threads:

    Windows:

    • Use Process Explorer to see threads within a specific process
    • Right-click on a process → Properties → Threads tab

    Linux/Unix:

    # View threads for a specific process ps -T -p <PID> # Or use top with thread view top -H -p <PID>

    Java-specific:

    # List Java threads jstack <PID> # Or use jcmd jcmd <PID> Thread.print
    12

    Which type of multitasking is better and why?

    Thread-based multitasking is generally better for the following reasons:

    AspectProcess-basedThread-based
    Memory UsageHigh (separate address space)Low (shared address space)
    Context SwitchingExpensiveInexpensive
    CommunicationComplex (IPC mechanisms)Simple (shared memory)
    Creation OverheadHighLow
    Resource SharingDifficultEasy

    Advantages of Thread-based Multitasking:

    • Lower memory footprint
    • Faster context switching
    • Easier inter-thread communication
    • Better resource utilization
    • Simpler programming model

    When to use Process-based:

    • When you need complete isolation between tasks
    • For security-critical applications
    • When one process crash shouldn't affect others
    13

    What is the difference between `start()` and `run()`?

    • start(): This method is used to begin the execution of a new thread. It registers the thread with the thread scheduler, allocates resources, and invokes the run() method in a separate call stack. Calling start() on a thread more than once will throw an IllegalThreadStateException.
    • run(): This method contains the code that constitutes the thread's task. If you call run() directly, it executes in the current thread, just like any other method call. No new thread is created.
    14

    What are the tasks performed by the start() method?

    The start() method performs two primary tasks:

    1. Thread Registration: Registers the thread with the thread scheduler so the scheduler knows what the thread should do, when it should run, and how it should be scheduled
    2. Method Invocation: Calls the corresponding run() method for the thread

    Important Notes:

    • The start() method creates a new call stack for the thread
    • It allocates necessary resources for thread execution
    • The actual execution is handled by the thread scheduler, not the start() method itself
    15

    Can you start a thread twice? What happens if you try?

    No, you cannot start a thread twice. Attempting to call start() on a thread that has already been started will result in an IllegalThreadStateException.

    Thread thread = new Thread(() -> System.out.println("Running")); thread.start(); // This works fine thread.start(); // This throws IllegalThreadStateException

    Why this restriction exists:

    • A thread can only be in the NEW state once
    • Once started, it transitions to RUNNABLE and cannot go back to NEW
    • This prevents resource leaks and ensures proper thread lifecycle management

    Solution: Create a new Thread object if you need to run the same task again.

    16

    What happens if you call run() instead of start()?

    If you call run() directly instead of start(), the following happens:

    1. No new thread is created - the code runs in the current thread (usually the main thread)
    2. Sequential execution - the run() method executes like any normal method call
    3. No concurrency - the program becomes single-threaded
    Thread thread = new Thread(() -> { System.out.println("Thread: " + Thread.currentThread().getName()); }); thread.run(); // Executes in main thread // Output: Thread: main thread.start(); // Creates new thread // Output: Thread: Thread-0

    Key Difference:

    • run() = method call in current thread
    • start() = creates new thread and calls run() in that thread
    17

    Explain the lifecycle of a thread.

    A thread in Java goes through several states:

    • NEW: The thread object has been created, but start() has not yet been called.
    • RUNNABLE: The thread is ready to run and is waiting for CPU time. This state includes both "ready" and "running".
    • BLOCKED: The thread is waiting to acquire a monitor lock to enter a synchronized block/method.
    • WAITING: The thread is waiting indefinitely for another thread to perform a particular action (e.g., after calling Object.wait() or Thread.join()).
    • TIMED_WAITING: The thread is waiting for a specified amount of time (e.g., after calling Thread.sleep(long) or Object.wait(long)).
    • TERMINATED: The thread has completed its execution (its run() method has exited).
    18

    What is the purpose of the `join()` method?

    The thread.join() method causes the current thread to pause its execution until the thread t has completed. This is a way to ensure that a task in one thread is completed before another thread proceeds. Overloaded versions allow you to specify a timeout.

    Thread worker = new Thread(() -> { // ... do some work }); worker.start(); worker.join(); // The main thread waits here until the worker thread is terminated. System.out.println("Worker thread finished.");
    19

    What is the difference between `sleep()` and `wait()`?

    Featuresleep()wait()
    Classjava.lang.Thread (static method)java.lang.Object (instance method)
    Lock ReleaseDoes not release the monitor lock.Releases the monitor lock.
    ContextCan be called from any context.Must be called from within a synchronized block or method.
    Waking upWakes up automatically after the specified time.Wakes up only when notify() or notifyAll() is called on the same object, or due to a timeout.
    PurposePauses execution for a specified time.Used for inter-thread communication and coordination.
    20

    What is thread interruption and how do you handle it?

    Interruption is a mechanism to signal a thread that it should stop what it's doing and do something else. A thread is interrupted by calling interrupt() on its Thread object.

    A thread can check if it has been interrupted using isInterrupted() (which doesn't clear the interrupted status) or Thread.interrupted() (which is static and clears the status).

    Methods like sleep(), wait(), and join() throw an InterruptedException when the waiting/sleeping thread is interrupted, providing a way to handle the interruption request.

    Thread task = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { System.out.println("Working..."); try { Thread.sleep(1000); } catch (InterruptedException e) { // Interruption received while sleeping System.out.println("Interrupted! Cleaning up and stopping."); Thread.currentThread().interrupt(); // Re-interrupt the thread to set the flag } } }); task.start(); // After some time... task.interrupt(); // Request the thread to stop
    21

    What does `Thread.yield()` do?

    Thread.yield() is a hint to the thread scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint. It's a way to suggest that other threads of the same priority should be allowed to run. Its use is rare and it's not a reliable way to control thread execution.

    22

    What is a `ThreadLocal`?

    ThreadLocal provides thread-local variables. Each thread that accesses a ThreadLocal variable has its own, independently initialized copy of the variable. This is a way to achieve thread-safety for a mutable object without resorting to synchronization, as each thread operates on its own instance.

    Common use cases include storing user-specific data (e.g., transaction ID, user session) throughout the execution of a request in a server application.

    23

    What are the problems with ThreadLocal and how can you avoid them?

    Common ThreadLocal Problems:

    1. Memory Leaks: ThreadLocal variables are not automatically garbage collected when the thread dies, especially in thread pools
    2. Inheritance Issues: Child threads don't inherit ThreadLocal values from parent threads
    3. Unconstrained Mutability: Any code can modify ThreadLocal values at any time
    4. Unbounded Lifetime: Values persist for the entire thread lifetime

    Solutions:

    // Always clean up ThreadLocal values ThreadLocal<String> threadLocal = new ThreadLocal<>(); try { threadLocal.set("value"); // Use the value } finally { threadLocal.remove(); // Important: clean up } // Use InheritableThreadLocal for inheritance InheritableThreadLocal<String> inheritable = new InheritableThreadLocal<>();

    Modern Alternative: Use ScopedValue (Java 25+) which provides automatic cleanup and better security.

    Related: See our deep-dive: Scoped Values in Java 25: Cleaner and Safer Way to Share Data in Concurrent Applications

    24

    What is the difference between notify() and notifyAll()?

    Aspectnotify()notifyAll()
    Threads WokenOnly one arbitrary threadAll waiting threads
    Use CaseWhen you know only one thread should proceedWhen multiple threads might need to proceed
    PerformanceSlightly better (wakes only one)More overhead (wakes all)
    SafetyCan cause starvationPrevents starvation

    Example:

    synchronized(lock) { if (condition) { lock.wait(); // Thread waits here } // After notify() or notifyAll() } // In another thread synchronized(lock) { condition = true; lock.notify(); // Wakes one thread // OR lock.notifyAll(); // Wakes all threads }

    Best Practice: Use notifyAll() unless you have a specific reason to use notify().

    25

    What is the difference between wait() and sleep()?

    Featurewait()sleep()
    ClassObject class methodThread class method
    Lock ReleaseReleases the lockDoes not release the lock
    ContextMust be in synchronized blockCan be called anywhere
    Waking UpOnly by notify() or notifyAll()Automatically after timeout
    PurposeInter-thread communicationPause execution
    26

    What is the difference between yield() and sleep()?

    Featureyield()sleep()
    PurposeHints scheduler to yield CPUPauses execution for specific time
    TimeNo guaranteed timeGuaranteed minimum time
    Thread StateRemains RUNNABLEChanges to TIMED_WAITING
    ReliabilityScheduler may ignore hintAlways pauses execution
    Use CaseVoluntary CPU sharingFixed time delays
    27

    What is the difference between wait() and join()?

    Featurewait()join()
    PurposeInter-thread communicationWait for thread completion
    Lock RequiredMust be in synchronized blockNo synchronization required
    Waking Upnotify() or notifyAll()Thread completes execution
    Timeoutwait(long timeout)join(long timeout)
    Use CaseProducer-consumer patternsSequential execution
    28

    What is the difference between interrupt() and stop()?

    Featureinterrupt()stop() (deprecated)
    SafetySafe, cooperativeUnsafe, forceful
    Thread StateSets interrupt flagImmediately terminates
    Resource CleanupAllows cleanupNo cleanup opportunity
    StatusCurrent and recommendedDeprecated since Java 1.2
    ExceptionThrows InterruptedExceptionNo exception handling

    Why stop() is deprecated:

    • Can leave objects in inconsistent state
    • No opportunity for cleanup
    • Can cause deadlocks
    • Unsafe for general use

    Use interrupt() instead:

    Thread thread = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { // Do work } // Cleanup code }); thread.interrupt(); // Safe way to stop
    29

    What happens if a `run()` method throws an uncaught exception?

    If an uncaught exception is thrown from a thread's run() method, the thread will terminate. You can define a global handler for such exceptions using Thread.setDefaultUncaughtExceptionHandler() or a specific one for a thread using thread.setUncaughtExceptionHandler(). Without a handler, the exception stack trace is typically printed to the console.

    30

    What is the Executor Framework?

    The Executor Framework, part of the java.util.concurrent package, provides a high-level API for creating and managing threads. It decouples task submission from the mechanics of how each task will be run, including details of thread creation, scheduling, and lifecycle management.

    Key interfaces:

    • Executor: A simple interface with a single execute(Runnable) method.
    • ExecutorService: A sub-interface of Executor that adds features for managing the lifecycle of the executor and the tasks (e.g., submit(), shutdown()).
    • ScheduledExecutorService: A sub-interface of ExecutorService that can schedule commands to run after a given delay, or to execute periodically.

    Related: Learn practical examples of using the Executor Framework in our Spring Boot Concurrency Guide.

    31

    Why is it better to use the Executor Framework than to create threads directly?

    • Improved Performance: It promotes the use of thread pools, which avoids the overhead of creating a new thread for each task. Reusing threads is much more efficient.
    • Resource Management: It allows you to control the number of threads, preventing resource exhaustion (e.g., running out of memory or threads).
    • Decoupling: It separates task logic (Runnable, Callable) from execution policy.
    • Advanced Features: Provides out-of-the-box support for futures, task scheduling, and different queuing policies.
    32

    What are the main types of thread pools you can create with the `Executors` factory class?

    • newFixedThreadPool(int nThreads): Creates a thread pool that reuses a fixed number of threads. If all threads are active, new tasks will wait in a queue.
    • newCachedThreadPool(): Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. It's suitable for executing many short-lived asynchronous tasks.
    • newSingleThreadExecutor(): Creates an executor that uses a single worker thread. Tasks are guaranteed to execute sequentially.
    • newScheduledThreadPool(int corePoolSize): Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.
    33

    What is the difference between `execute()` and `submit()` on an `ExecutorService`?

    • execute(Runnable): Takes a Runnable object. It's "fire and forget"; you cannot get the result of the task or check if it completed successfully. It returns void.
    • submit(Runnable) or submit(Callable): Can take both Runnable and Callable tasks. It returns a Future object, which can be used to check the status of the task, block until it's complete, and retrieve its result (if it was a Callable).
    34

    What is a `Future`?

    A Future represents the result of an asynchronous computation. It provides methods to:

    • isDone(): Check if the computation is complete.
    • isCancelled(): Check if the task was cancelled.
    • cancel(boolean): Attempt to cancel the task.
    • get(): Wait for the computation to complete and then retrieve its result. This is a blocking call.
    • get(long, TimeUnit): Wait for a specified time for the result.
    35

    What is the difference between `Runnable` and `Callable`?

    FeatureRunnableCallable
    IntroducedJava 1.0Java 5.0
    Methodvoid run()V call()
    Return ValueCannot return a value.Can return a value of type V.
    ExceptionCannot throw checked exceptions.Can throw checked exceptions.
    UsageUsed with Thread and Executor.execute().Used with ExecutorService.submit().
    36

    What is the difference between Future and CompletableFuture?

    FeatureFutureCompletableFuture
    BlockingAlways blockingNon-blocking with callbacks
    CompositionNo compositionRich composition methods
    Exception HandlingBasicAdvanced (exceptionally, handle)
    Manual CompletionNoYes
    ChainingNoYes (thenApply, thenCompose)
    Multiple ResultsNoYes (allOf, anyOf)

    Future Example:

    Future<String> future = executor.submit(() -> "Result"); String result = future.get(); // Blocking

    CompletableFuture Example:

    CompletableFuture<String> future = CompletableFuture .supplyAsync(() -> "Result") .thenApply(s -> s.toUpperCase()) .exceptionally(throwable -> "Error"); // Non-blocking with callbacks
    37

    What is the difference between Thread.sleep() and Object.wait()?

    FeatureThread.sleep()Object.wait()
    ClassThread class methodObject class method
    LockDoes not release lockReleases the lock
    ContextCan be called anywhereMust be in synchronized block
    WakingAutomatic after timeoutOnly by notify() or notifyAll()
    PurposePause executionInter-thread communication
    38

    What is the difference between Thread.sleep() and Thread.yield()?

    FeatureThread.sleep()Thread.yield()
    GuaranteeGuaranteed pauseHint to scheduler
    TimeSpecific time durationNo time guarantee
    Thread StateChanges to TIMED_WAITINGRemains RUNNABLE
    ReliabilityAlways pausesScheduler may ignore
    Use CaseFixed delaysVoluntary CPU sharing
    39

    What is the difference between Thread.sleep() and Thread.join()?

    FeatureThread.sleep()Thread.join()
    PurposePause current threadWait for another thread
    TargetSelfAnother thread
    ConditionTime-basedThread completion
    InterruptionCan be interruptedCan be interrupted
    Use CaseDelaysSequential execution
    40

    What is the difference between Thread.sleep() and LockSupport.park()?

    FeatureThread.sleep()LockSupport.park()
    PurposePause for specific timePause until unparked
    TimeTime-basedEvent-based
    InterruptionThrows InterruptedExceptionReturns silently
    Use CaseDelaysLow-level synchronization
    LevelHigh-levelLow-level
    41

    What is the difference between Thread.sleep() and TimeUnit.sleep()?

    FeatureThread.sleep()TimeUnit.sleep()
    UnitsMilliseconds onlyMultiple time units
    ReadabilityLess readableMore readable
    FlexibilityLimitedMore flexible
    ExceptionInterruptedExceptionInterruptedException

    Example:

    // Thread.sleep() - milliseconds only Thread.sleep(1000); // TimeUnit.sleep() - multiple units TimeUnit.SECONDS.sleep(1); TimeUnit.MINUTES.sleep(1); TimeUnit.HOURS.sleep(1);
    42

    What is the difference between Thread.sleep() and ScheduledExecutorService?

    FeatureThread.sleep()ScheduledExecutorService
    PurposePause current threadSchedule future execution
    ThreadBlocks current threadUses separate thread
    ReusabilityOne-timeReusable
    PrecisionLess preciseMore precise
    Use CaseSimple delaysComplex scheduling
    43

    What is the difference between Thread.sleep() and CountDownLatch.await()?

    FeatureThread.sleep()CountDownLatch.await()
    PurposePause for timeWait for countdown
    ConditionTime-basedEvent-based
    InterruptionCan be interruptedCan be interrupted
    Use CaseDelaysSynchronization
    FlexibilityFixed timeDynamic condition
    44

    What is `ThreadPoolExecutor`?

    ThreadPoolExecutor is the core, highly configurable class behind the factory methods of Executors. It allows fine-grained control over the thread pool's behavior.

    Key constructor parameters:

    • corePoolSize: The number of threads to keep in the pool, even if they are idle.
    • maximumPoolSize: The maximum number of threads allowed in the pool.
    • keepAliveTime: When the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
    • workQueue: The queue to use for holding tasks before they are executed.
    • threadFactory: The factory to use when the executor creates a new thread.
    • handler: The handler to use when a task is rejected.
    45

    Explain the task rejection policies for `ThreadPoolExecutor`.

    When the work queue is full and the number of threads has reached maximumPoolSize, the RejectedExecutionHandler is invoked. The default policies are:

    • AbortPolicy (default): Throws a RejectedExecutionException.
    • CallerRunsPolicy: The task is executed by the calling thread itself.
    • DiscardPolicy: The task is silently discarded.
    • DiscardOldestPolicy: The oldest task in the queue is discarded, and the new task is submitted.
    46

    How do you gracefully shut down an `ExecutorService`?

    You should use shutdown() and awaitTermination().

    1. shutdown(): Initiates an orderly shutdown. The executor stops accepting new tasks, but previously submitted tasks will be executed.
    2. awaitTermination(long, TimeUnit): Blocks until all tasks have completed after a shutdown request, or the timeout occurs, or the current thread is interrupted.
    ExecutorService executor = Executors.newFixedThreadPool(10); // ... submit tasks executor.shutdown(); try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // Forcefully shut down } } catch (InterruptedException e) { executor.shutdownNow(); }
    47

    What is the difference between `shutdown()` and `shutdownNow()`?

    • shutdown(): Allows currently running tasks and tasks in the queue to complete.
    • shutdownNow(): Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution. It does this by interrupting the worker threads.
    48

    What is a `ScheduledExecutorService`?

    It's an ExecutorService that can schedule tasks to be executed in the future.

    • schedule(Callable<V>, long, TimeUnit): Executes a Callable once after a given delay.
    • schedule(Runnable, long, TimeUnit): Executes a Runnable once after a given delay.
    • scheduleAtFixedRate(Runnable, long, long, TimeUnit): Executes a task periodically after an initial delay. The next task is scheduled relative to the start of the previous execution. If a task takes longer than the period, the next one will start immediately after the current one finishes.
    • scheduleWithFixedDelay(Runnable, long, long, TimeUnit): Executes a task periodically. The next task is scheduled relative to the end of the previous execution.
    49

    What is a Fork/Join Pool?

    Introduced in Java 7, the Fork/Join framework is designed for work that can be broken into smaller pieces recursively. It uses a special ForkJoinPool executor which implements a "work-stealing" algorithm: idle threads try to "steal" tasks from the deques (double-ended queues) of busy threads. This improves CPU utilization.

    It's ideal for CPU-intensive tasks like data processing, searching, and sorting on multi-core machines.

    50

    What are `RecursiveTask` and `RecursiveAction`?

    These are the base classes for tasks that run within a ForkJoinPool.

    • RecursiveTask<V>: A task that returns a result. Its compute() method should be overridden.
    • RecursiveAction: A task that does not return a result (similar to Runnable).
    51

    Scenario: You need to process 1000 independent, I/O-bound tasks (e.g., REST API calls). Which thread pool would you choose and why?

    A CachedThreadPool or a FixedThreadPool with a large number of threads would be a good choice.

    • Reasoning: I/O-bound tasks spend most of their time waiting for the network or disk. During this waiting time, the thread is blocked and the CPU is idle.
    • CachedThreadPool: It can create a large number of threads to handle many concurrent I/O operations. Since the threads are mostly waiting, having many of them doesn't overload the CPU. It will reuse threads that finish their tasks.
    • FixedThreadPool: You could also use a FixedThreadPool, but the size would need to be tuned. A common formula for I/O-bound tasks is NumberOfThreads = NumberOfCores * (1 + WaitTime / ServiceTime). Since wait time is high, the pool size should be significantly larger than the number of CPU cores.

    Using a small fixed-size pool would lead to tasks waiting in the queue while the CPU is underutilized.

    52

    Scenario: You need to run a CPU-intensive task on a machine with 8 cores. Which thread pool is most appropriate?

    A FixedThreadPool with 8 threads (Executors.newFixedThreadPool(8)).

    • Reasoning: For CPU-intensive tasks, the optimal number of threads is typically equal to the number of available CPU cores. Having more threads than cores would lead to context switching overhead without any performance benefit, as there are no more cores to run the extra threads on. The Fork/Join pool is also an excellent choice, especially if the task can be recursively decomposed.
    53

    What is synchronization?

    Synchronization is a mechanism to control the access of multiple threads to any shared resource. It's a core tool for preventing concurrency issues like race conditions and memory consistency errors. Java provides synchronization via the synchronized keyword and the java.util.concurrent.locks package.

    54

    What is a monitor (or intrinsic lock)?

    In Java, every object has an intrinsic lock (or monitor) associated with it. The synchronized keyword works by acquiring and releasing this lock. When a thread enters a synchronized method or block, it acquires the monitor of the corresponding object. Other threads attempting to acquire the same monitor will be blocked until the lock is released.

    55

    What is the difference between a synchronized method and a synchronized block?

    • Synchronized Method: Locks the entire method. The lock is on the this object for instance methods, or the Class object for static methods. It's simple to use but can lead to performance issues if the method is long and only a small part of it needs protection.
      public synchronized void doWork() { // entire method is locked }
    • Synchronized Block: Locks only a specific section of code. It allows you to specify the lock object explicitly, which provides more granular control. This is generally preferred for better performance.
      public void doWork() { // ... non-critical section synchronized(this) { // critical section } // ... non-critical section }
    56

    What is a race condition? Give an example.

    A race condition occurs when multiple threads access and manipulate shared data concurrently, and the final outcome depends on the particular order in which the threads are scheduled.

    Example: A simple counter.

    class Counter { private int count = 0; public void increment() { count++; // This is not atomic! It's read-modify-write. } }

    If two threads call increment() at the same time, they might both read the value of count (e.g., 5), both increment it to 6, and both write 6 back. The final value will be 6 instead of the correct 7.

    57

    What is a deadlock? How can you prevent it?

    A deadlock is a situation where two or more threads are blocked forever, waiting for each other to release the resources they need.

    Prevention Strategies:

    1. Avoid Nested Locks: Try to avoid acquiring multiple locks at once.
    2. Lock Ordering: If you must acquire multiple locks, ensure that all threads acquire them in the same fixed order.
    3. Use tryLock: Use lock.tryLock(timeout, unit) from the Lock interface. This allows a thread to back off if it cannot acquire a lock within a certain time, preventing it from holding one lock while waiting indefinitely for another.
    4. Avoid unnecessary locking.
    58

    What is a `ReentrantLock`?

    A ReentrantLock is a concrete implementation of the Lock interface. It has the same basic behavior as a synchronized block but with extended capabilities:

    • Reentrancy: A thread that already holds the lock can re-acquire it without blocking.
    • Fairness: You can create a "fair" lock where the longest-waiting thread is guaranteed to get the lock next.
    • Interruptible Lock Acquisition: A thread waiting for a lock can be interrupted.
    • Timed Lock Acquisition: tryLock() allows a thread to attempt to acquire a lock for a certain period.
    • Condition Objects: It can be associated with Condition objects, which are more flexible than wait/notify.
    59

    What does "reentrant" mean in the context of `ReentrantLock`?

    Reentrancy means that a thread can acquire a lock it already holds. The lock maintains a hold count. Each time the thread acquires the lock, the count is incremented. Each time it releases the lock (via unlock()), the count is decremented. The lock is only fully released when the count reaches zero. This is crucial for preventing a thread from deadlocking with itself in recursive calls.

    60

    `synchronized` vs. `ReentrantLock`: which one to prefer?

    • For most common use cases, synchronized is simpler, less error-prone, and often optimized by the JVM. It should be your default choice.
    • Use ReentrantLock when you need its advanced features:
      • Timed or interruptible lock waiting.
      • Fair queuing.
      • The ability to use Condition objects.

    A key difference in practice is that unlock() must be called in a finally block to guarantee the lock is released, even if exceptions occur. synchronized handles this automatically.

    lock.lock(); try { // critical section } finally { lock.unlock(); }
    61

    What is a `ReadWriteLock`?

    A ReadWriteLock maintains a pair of associated locks—one for read-only operations and one for writing. The read lock may be held simultaneously by multiple reader threads, as long as there are no writers. The write lock is exclusive.

    This is a performance optimization for data structures that are read far more often than they are modified.

    62

    What is a `StampedLock`?

    Introduced in Java 8, StampedLock is a more advanced read-write lock. It supports three modes: reading, writing, and optimistic reading.

    • Writing: writeLock() returns a stamp. Exclusive access.
    • Reading: readLock() returns a stamp. Non-exclusive.
    • Optimistic Reading: tryOptimisticRead() returns a stamp without any locking. After reading, you call validate(stamp) to check if a write has occurred in the meantime. If so, you need to acquire a proper read lock and try again.

    Optimistic reading can provide significant performance gains in read-heavy scenarios by avoiding the overhead of acquiring a full read lock.

    63

    What is a `Semaphore`?

    A Semaphore is a synchronization aid that controls access to a shared resource through the use of a counter. It maintains a set of "permits".

    • acquire(): Blocks until a permit is available, and then takes one.
    • release(): Adds a permit, potentially releasing a blocking acquirer.

    A semaphore initialized with one permit is equivalent to a mutex lock. It's useful for limiting the number of concurrent threads accessing a specific part of your application (e.g., limiting concurrent database connections).

    64

    What is the difference between Semaphore and Mutex?

    FeatureSemaphoreMutex
    PermitsCan have multiple permitsOnly one permit (binary)
    OwnershipNo ownership conceptHas ownership (only owner can release)
    Use CaseResource pooling, rate limitingCritical section protection
    Initializationnew Semaphore(n)new Semaphore(1)
    ReleaseAny thread can releaseOnly the acquiring thread can release

    Example:

    // Semaphore - multiple permits Semaphore semaphore = new Semaphore(3); // 3 permits semaphore.acquire(); // Thread 1 semaphore.acquire(); // Thread 2 semaphore.acquire(); // Thread 3 // Thread 4 waits until one of the above releases // Mutex - single permit Semaphore mutex = new Semaphore(1); mutex.acquire(); // Only one thread at a time
    65

    What is the difference between CountDownLatch and CyclicBarrier?

    FeatureCountDownLatchCyclicBarrier
    ReusabilityOne-time useReusable (cyclic)
    DirectionOne-way (countdown)Two-way (meeting point)
    ThreadsOne or more wait for othersAll threads wait for each other
    ResetCannot be resetAutomatically resets
    ActionNo action on countdownOptional action on barrier trip

    CountDownLatch Example:

    CountDownLatch latch = new CountDownLatch(3); // 3 threads count down // 1 thread waits for all to complete

    CyclicBarrier Example:

    CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All threads reached the barrier")); // All 3 threads wait for each other // When all arrive, barrier action executes
    66

    What is the difference between ReentrantLock and synchronized?

    FeatureReentrantLocksynchronized
    FlexibilityMore flexibleLess flexible
    FairnessCan be fair or unfairAlways unfair
    InterruptionSupports interruptionNo interruption support
    TimeoutSupports tryLock(timeout)No timeout support
    ConditionsMultiple Condition objectsSingle wait/notify
    PerformanceSlightly slowerSlightly faster
    MemoryMore memory overheadLess memory overhead

    When to use ReentrantLock:

    • Need fairness
    • Need interruption support
    • Need timeout
    • Need multiple conditions
    • Need lock polling

    When to use synchronized:

    • Simple cases
    • Better performance needed
    • Less memory usage needed
    • Simpler code preferred
    67

    What is the difference between volatile and synchronized?

    Featurevolatilesynchronized
    ScopeSingle variableBlock or method
    AtomicityNo (except for simple assignments)Yes
    PerformanceFasterSlower
    BlockingNever blocksCan block
    Use CaseVisibility onlyAtomicity + Visibility

    volatile Example:

    private volatile boolean flag = false; // Only ensures visibility, not atomicity

    synchronized Example:

    private int counter = 0; public synchronized void increment() { counter++; // Atomic and visible }
    68

    What is the difference between AtomicInteger and synchronized int?

    FeatureAtomicIntegersynchronized int
    PerformanceFaster (lock-free)Slower (lock-based)
    BlockingNever blocksCan block
    MemoryMore memoryLess memory
    OperationsLimited atomic operationsAny operation
    ScalabilityBetter under contentionWorse under contention

    AtomicInteger Example:

    AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet(); // Atomic operation

    synchronized Example:

    private int counter = 0; public synchronized int increment() { return ++counter; // Atomic but blocking }
    69

    What is the difference between Executor and ExecutorService?

    FeatureExecutorExecutorService
    MethodsOnly execute(Runnable)execute(), submit(), shutdown(), etc.
    Future SupportNoYes
    Lifecycle ManagementNoYes
    Task TrackingNoYes
    Use CaseSimple task executionAdvanced task management

    Executor Example:

    Executor executor = Executors.newFixedThreadPool(5); executor.execute(() -> System.out.println("Task"));

    ExecutorService Example:

    ExecutorService executor = Executors.newFixedThreadPool(5); Future<String> future = executor.submit(() -> "Result"); executor.shutdown();
    70

    What is a `CountDownLatch`?

    A CountDownLatch is a synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. It is initialized with a count.

    • await(): Causes the current thread to wait until the latch's count reaches zero.
    • countDown(): Decrements the count of the latch.

    It is a one-time use object; its count cannot be reset. Useful for "start gates" (all threads wait for a signal to start) or "end gates" (a main thread waits for all workers to finish).

    71

    What is a `CyclicBarrier`?

    A CyclicBarrier is a reusable synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. Threads call await(), which blocks until all parties have invoked await() on the barrier. At that point, the barrier is "tripped," and all waiting threads are released.

    The barrier is "cyclic" because it can be reset and used again after the waiting threads are released. An optional Runnable can be provided to be executed once per barrier trip.

    72

    `CountDownLatch` vs. `CyclicBarrier`: What's the difference?

    FeatureCountDownLatchCyclicBarrier
    ReusabilityOne-time use. The count cannot be reset.Reusable. Resets automatically after all threads pass the barrier.
    WaitingOne or more threads can wait for a set of other threads to complete tasks.Threads wait for each other to reach a common point.
    DirectionIt's a one-way street. The main thread waits for workers.It's a meeting point. All threads are workers and wait for each other.
    ActionNo action is performed when the count reaches zero.An optional Runnable action can be executed when the barrier is tripped.
    73

    What is a `Phaser`?

    Introduced in Java 7, Phaser is a more flexible and powerful version of CyclicBarrier and CountDownLatch.

    • Dynamic Parties: The number of registered parties can change dynamically over time.
    • Phases: It supports multiple phases of execution, similar to a reusable CyclicBarrier.
    • Termination: Can be configured to terminate after a certain number of phases.

    It's useful for more complex, multi-phase parallel computations where the number of participating threads might vary.

    74

    What is optimistic vs. pessimistic locking?

    • Pessimistic Locking: Assumes that contention is likely and acquires an exclusive lock before accessing a resource (e.g., using synchronized or ReentrantLock). This can be inefficient if contention is rare.
    • Optimistic Locking: Assumes that contention is rare. It proceeds with an operation without acquiring a lock. Before committing the change, it verifies that no other thread has modified the data in the meantime (e.g., using a version number or timestamp). If a conflict is detected, the operation is retried. StampedLock's optimistic read mode is an example of this pattern.
    75

    What is lock striping?

    Lock striping is a technique to improve concurrency by using an array of locks instead of a single lock. Each hash bucket or segment of a data structure is protected by its own lock. This allows different threads to operate on different parts of the data structure simultaneously, as long as they don't need to access the same segment. ConcurrentHashMap uses this technique.

    76

    What is lock coarsening?

    Lock coarsening is a JVM optimization technique where a sequence of adjacent synchronized blocks that use the same lock object are merged into a single larger synchronized block. This reduces the overhead of repeatedly acquiring and releasing the lock.

    77

    What is lock elision?

    Lock elision is a JVM optimization where the JIT compiler can completely remove a lock acquisition if it determines that an object's lock is not actually contended by multiple threads (e.g., the object is thread-local and never escapes).

    78

    What are `Condition` objects?

    A Condition object (obtained from a Lock via lock.newCondition()) provides a more powerful and flexible alternative to the traditional wait(), notify(), and notifyAll() methods.

    • await(): Equivalent to wait().
    • signal(): Equivalent to notify().
    • signalAll(): Equivalent to notifyAll().

    The key advantage is that you can have multiple Condition objects associated with a single Lock. This allows for more fine-grained control, for example, in a bounded buffer problem, you can have one condition for "not full" and another for "not empty".

    79

    Scenario: Implement a thread-safe Singleton pattern.

    There are several ways. The "Initialization-on-demand holder idiom" is generally considered the best approach as it's lazy, thread-safe, and doesn't require synchronization.

    public class ThreadSafeSingleton { private ThreadSafeSingleton() {} private static class SingletonHolder { private static final ThreadSafeSingleton INSTANCE = new ThreadSafeSingleton(); } public static ThreadSafeSingleton getInstance() { return SingletonHolder.INSTANCE; } }

    Why it's thread-safe: The JVM guarantees that the static initializer for SingletonHolder is executed only once, when getInstance() is called for the first time, and that this initialization is thread-safe.

    80

    What is fair vs. non-fair locking?

    • Non-fair lock (default): When a lock is released, any waiting thread can acquire it, regardless of how long it has been waiting. This can lead to "barging", where a newly arrived thread acquires the lock before a thread that has been waiting for a long time. This approach prioritizes throughput. synchronized and the default ReentrantLock are non-fair.
    • Fair lock: The lock is granted to the longest-waiting thread. This prevents starvation but can significantly reduce throughput due to the overhead of managing the queue. You can create a fair lock with new ReentrantLock(true).
    81

    What is the `tryLock()` method in the `Lock` interface?

    lock.tryLock() attempts to acquire the lock immediately and returns true if successful, false otherwise. It does not block. An overloaded version tryLock(long time, TimeUnit unit) will try to acquire the lock for a specified duration before giving up. This is very useful for preventing deadlocks and for implementing responsive systems.

    82

    How do you detect a deadlock in Java?

    • Thread Dumps: The most common way. You can generate a thread dump using tools like jstack, jvisualvm, or by sending a SIGQUIT signal (on Linux/macOS). The thread dump will include information about which threads are deadlocked and the locks they are waiting for.
    • ThreadMXBean: Programmatically, you can use ThreadMXBean.findDeadlockedThreads() to get an array of thread IDs that are involved in a deadlock.
    83

    Scenario: Design a simple thread-safe bounded buffer (Producer-Consumer).

    Using ReentrantLock and Condition objects:

    class BoundedBuffer<T> { private final T[] buffer; private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); private int count, putIndex, takeIndex; public BoundedBuffer(int capacity) { buffer = (T[]) new Object[capacity]; } public void put(T item) throws InterruptedException { lock.lock(); try { while (count == buffer.length) { notFull.await(); // Buffer is full, wait for space } buffer[putIndex] = item; if (++putIndex == buffer.length) putIndex = 0; count++; notEmpty.signal(); // Signal that buffer is no longer empty } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lock(); try { while (count == 0) { notEmpty.await(); // Buffer is empty, wait for item } T item = buffer[takeIndex]; if (++takeIndex == buffer.length) takeIndex = 0; count--; notFull.signal(); // Signal that buffer is no longer full return item; } finally { lock.unlock(); } } }
    84

    What is the Java Memory Model (JMM)?

    The JMM defines the rules that govern how threads interact through memory. It specifies when changes to a variable made by one thread become visible to other threads. Without the JMM, it would be impossible to write correct, reliable concurrent code on modern multi-core, multi-level cache architectures. It's an abstraction over how hardware memory actually works.

    85

    What are the three key properties defined by the JMM?

    1. Atomicity: An operation is atomic if it completes entirely or not at all. In Java, reads and writes to most primitive types (except long and double) are atomic. Operations like i++ are not atomic.
    2. Visibility: When one thread modifies a shared variable, other threads are able to see the change. volatile and synchronized keywords are used to ensure visibility.
    3. Ordering: The JMM allows the compiler and CPU to reorder instructions to optimize performance. The "happens-before" relationship defines a partial ordering on all actions within the program, ensuring that memory operations are perceived to occur in a consistent order.
    86

    What is the "happens-before" relationship?

    A "happens-before" relationship is a guarantee that memory writes by one specific statement are visible to another specific statement. If action A happens-before action B, then the results of A are visible to and ordered before B.

    Key happens-before rules:

    • An unlock on a monitor happens-before every subsequent lock on that same monitor.
    • A write to a volatile variable happens-before every subsequent read of that same volatile variable.
    • A call to start() on a thread happens-before any action in the started thread.
    • All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
    87

    `synchronized` vs. `volatile`: What's the difference?

    Featuresynchronizedvolatile
    GuaranteesAtomicity and VisibilityVisibility only
    MechanismAcquires and releases a monitor lock.Uses memory barriers.
    ScopeCan protect blocks of code and methods.Applies only to a single variable.
    BlockingCan cause threads to block.Never causes threads to block.
    OverheadHigher overhead due to locking.Lower overhead.
    88

    What is instruction reordering?

    To improve performance, compilers, the JIT, and CPUs can reorder instructions that have no data dependency. For example, two independent variable assignments might be swapped. While this is safe for single-threaded code, it can cause unpredictable behavior in multi-threaded code if not handled correctly with synchronization mechanisms. volatile and synchronized prevent harmful reordering.

    89

    What are atomic variables (e.g., `AtomicInteger`)?

    The java.util.concurrent.atomic package provides classes like AtomicInteger, AtomicLong, and AtomicReference that support lock-free, thread-safe programming on single variables. They use low-level, hardware-specific atomic instructions like Compare-And-Swap (CAS) instead of locks.

    This is much more efficient than using synchronized for simple atomic operations like incrementing a counter.

    // Thread-safe counter using AtomicInteger AtomicInteger atomicCounter = new AtomicInteger(0); atomicCounter.incrementAndGet(); // Atomically increments and returns the new value
    90

    What is a Compare-And-Swap (CAS) operation?

    CAS is an atomic instruction provided by most modern CPUs. It takes three operands: a memory location V, an expected old value A, and a new value B. The instruction atomically updates the value in V to B only if the current value in V matches A. It returns the original value of V. This allows an algorithm to check if a value has changed since it was last read before updating it. Atomic variables use CAS internally.

    91

    What is safe publication?

    Safe publication is about ensuring that an object's state is fully visible to other threads after its construction. An object is safely published if:

    • It is initialized by a static initializer.
    • Its reference is stored into a volatile field or an AtomicReference.
    • Its reference is stored into a field that is guarded by a lock.
    • It is placed into a concurrent collection.

    Improper publication (e.g., assigning a new object to a plain shared variable without synchronization) can lead to other threads seeing a partially constructed object.

    92

    What are the guarantees for `final` fields in the JMM?

    The JMM provides special guarantees for final fields to ensure safe initialization. Once an object's constructor finishes, any thread that sees a reference to that object is guaranteed to see the correct values of its final fields, without needing any explicit synchronization. This is why String objects are immutable and thread-safe.

    93

    Why do we need concurrent collections?

    Standard collections like ArrayList and HashMap are not thread-safe. If multiple threads modify them concurrently, their internal state can become corrupted, leading to exceptions (ConcurrentModificationException) or incorrect behavior.

    While you can wrap them with Collections.synchronizedMap() or Collections.synchronizedList(), these "synchronized collections" use a single lock to protect the entire collection, leading to poor performance under high contention. Concurrent collections are designed for much better performance in multi-threaded environments.

    94

    What is `ConcurrentHashMap` and how is it better than a synchronized `HashMap`?

    ConcurrentHashMap is a high-performance, thread-safe implementation of the Map interface.

    • Fine-Grained Locking: Instead of a single lock for the entire map, it uses a technique called lock striping (in older versions) or node-level locking (in modern versions). This allows multiple threads to read and write to different parts of the map concurrently.
    • Non-blocking Reads: Read operations are generally non-blocking and do not require locks, providing very high-speed access.
    • No ConcurrentModificationException: Its iterators are weakly consistent and will never throw this exception.
    95

    What is `CopyOnWriteArrayList`? When should you use it?

    CopyOnWriteArrayList is a thread-safe variant of ArrayList. All mutative operations (add, set, remove) are implemented by making a fresh copy of the underlying array.

    • Pros: Read operations are extremely fast as they don't require any locking and operate on an immutable snapshot. Its iterators are safe from modification.
    • Cons: Write operations are very expensive due to the array copying.

    Use Case: It's ideal for collections that are read far more often than they are modified, such as listener lists.

    96

    What is a `BlockingQueue`?

    A BlockingQueue is a queue that supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element. It's a fundamental building block for producer-consumer patterns.

    Key methods:

    • put(e): Blocks until space is available, then adds the element.
    • take(): Blocks until an element is available, then retrieves and removes it.
    • offer(e, time, unit): Tries to add an element, waiting up to a specified time if necessary.
    • poll(time, unit): Tries to retrieve an element, waiting up to a specified time if necessary.
    97

    Name some implementations of `BlockingQueue`.

    • ArrayBlockingQueue: A fixed-size blocking queue backed by an array. It offers fairness policy.
    • LinkedBlockingQueue: An optionally bounded blocking queue backed by a linked list. It has higher throughput than ArrayBlockingQueue but is less predictable in performance.
    • PriorityBlockingQueue: An unbounded blocking queue that uses priority ordering for its elements.
    • SynchronousQueue: A queue with zero capacity. A put() operation must wait for a corresponding take() operation, and vice-versa. It's used for handoff scenarios.
    • DelayQueue: An unbounded blocking queue of Delayed elements, where an element can only be taken when its delay has expired.
    98

    What is the difference between `ArrayBlockingQueue` and `LinkedBlockingQueue`?

    FeatureArrayBlockingQueueLinkedBlockingQueue
    Data StructureArrayLinked List
    BoundsAlways bounded (fixed capacity).Optionally bounded (can be unbounded).
    LockingSingle lock for both put and take.Two locks: one for put, one for take.
    PerformanceGenerally lower throughput due to single lock.Generally higher throughput as producers and consumers can operate in parallel.
    FairnessCan be configured to be fair.Not fair.
    99

    What is a `ConcurrentSkipListMap`?

    A ConcurrentSkipListMap is a concurrent, sorted map. It's the concurrent equivalent of TreeMap. It uses a skip list data structure to achieve scalability. It allows for concurrent access and modification while maintaining sorted order.

    100

    What is a `ConcurrentNavigableMap`?

    It's a sub-interface of ConcurrentMap that provides navigation methods for a sorted map, such as headMap, tailMap, subMap, ceilingEntry, floorEntry. ConcurrentSkipListMap is the main implementation of this interface.

    101

    What is a `BlockingDeque`?

    A BlockingDeque is a "double-ended queue" that supports blocking operations. You can add or remove elements from both the head and the tail, and it will block if the deque is full (on insertion) or empty (on removal). LinkedBlockingDeque is the primary implementation.

    102

    What does "weakly consistent" mean for iterators in concurrent collections?

    The iterators of collections like ConcurrentHashMap and CopyOnWriteArrayList are weakly consistent. This means:

    • They will never throw ConcurrentModificationException.
    • They are guaranteed to traverse elements as they existed at some point since the iterator was created.
    • They may or may not reflect modifications made to the collection after the iterator was created.
    103

    Scenario: You need a queue for a producer-consumer system where producers should be blocked if they are much faster than consumers. Which collection would you use?

    A bounded BlockingQueue, such as ArrayBlockingQueue or a bounded LinkedBlockingQueue.

    • Reasoning: A bounded queue provides a natural back-pressure mechanism. When the queue is full, the put() method will block, automatically throttling the producers until the consumers catch up and free up space in the queue. This prevents the application from running out of memory by queuing an infinite number of tasks.
    104

    What is `Collections.synchronizedMap()` and why is `ConcurrentHashMap` better?

    Collections.synchronizedMap() is a factory method that returns a thread-safe wrapper around a regular Map. It achieves thread safety by synchronizing every single method on the map object's monitor.

    ConcurrentHashMap is better because:

    • Performance: It uses fine-grained locking, allowing for a much higher degree of concurrency. The synchronized map allows only one thread to access the map at a time, for any operation, creating a major bottleneck.
    • Iterator Safety: The synchronized map's iterator is not thread-safe and requires manual locking during iteration to prevent ConcurrentModificationException, which is cumbersome and inefficient. ConcurrentHashMap's iterator is weakly consistent and safe to use.
    105

    When would you use a `SynchronousQueue`?

    A SynchronousQueue is used when you want to hand off a task directly from a producer thread to a consumer thread, without any buffering. It's an excellent tool for managing handoffs in thread pools. For example, a CachedThreadPool uses a SynchronousQueue internally. When a task is submitted, it's queued. If a worker thread is available to take it, the handoff happens immediately. If not, a new thread is created to handle the task.

    106

    What is a `DelayQueue`? Give a use case.

    A DelayQueue is a BlockingQueue where elements can only be taken when their delay has expired. Elements must implement the Delayed interface, which has a getDelay() method.

    Use Case: Implementing a simple cache where items expire after a certain time. You can add items to a DelayQueue with their expiration time. A background thread can then take() from the queue, and it will receive expired items, which it can then remove from the cache.

    107

    How can you create a thread-safe `Set`?

    • Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>()): This is the standard, high-performance way to create a thread-safe Set backed by a ConcurrentHashMap.
    • CopyOnWriteArraySet: A thread-safe set suitable for read-heavy scenarios.
    • Collections.synchronizedSet(): A wrapper that provides basic thread safety with lower concurrency.
    108

    What is `CompletableFuture` and how is it an improvement over `Future`?

    CompletableFuture, introduced in Java 8, is an advanced implementation of Future. It provides a vast array of methods for composing, combining, and handling asynchronous computations in a non-blocking way.

    Key Improvements:

    • Non-blocking: You can chain operations and define callbacks that execute automatically when the future completes, without needing to call the blocking get() method.
    • Composability: You can combine multiple CompletableFuture instances (e.g., thenCombine, allOf, anyOf).
    • Exception Handling: Provides explicit methods for handling exceptions (e.g., exceptionally, handle).
    • Manual Completion: You can create a CompletableFuture and complete it manually later with a value or an exception.

    Related: See practical examples of CompletableFuture usage in our Spring Boot Concurrency Guide.

    109

    Explain `thenApply()`, `thenAccept()`, and `thenRun()` in `CompletableFuture`.

    These are methods for chaining actions to be executed upon completion of a future.

    • thenApply(Function): Takes the result of the completed future, applies a function to it, and returns a new CompletableFuture with the function's result. (Transforming the result).
    • thenAccept(Consumer): Takes the result of the completed future and passes it to a Consumer. It returns CompletableFuture<Void>. (Consuming the result).
    • thenRun(Runnable): Executes a Runnable after the future completes. It doesn't get the result. It returns CompletableFuture<Void>. (Action after completion).
    110

    How would you combine two `CompletableFuture` instances?

    • thenCombine(other, BiFunction): Waits for both futures to complete, then combines their results using a BiFunction.
    • thenCompose(Function): A powerful method for chaining dependent futures. If the next action itself returns a CompletableFuture, thenCompose is used to flatten the result (CompletableFuture<CompletableFuture<T>> becomes CompletableFuture<T>).
    • allOf(futures...): Returns a new CompletableFuture that completes when all of the given futures complete.
    • anyOf(futures...): Returns a new CompletableFuture that completes when any of the given futures complete.
    111

    What is thread starvation?

    Thread starvation occurs when a thread is perpetually denied access to resources it needs to make progress. This can happen if:

    • Higher-priority threads continuously preempt it.
    • In a non-fair lock, it continuously loses the race for the lock to other "barging" threads.
    • It is waiting for an object that other threads never make available.
    112

    What is a livelock?

    A livelock is a situation where two or more threads are actively trying to resolve a contention issue but are unable to make progress. The threads are not blocked; they are busy responding to each other's actions. For example, two people trying to pass in a hallway might repeatedly step aside in the same direction, blocking each other indefinitely.

    113

    What is Amdahl's Law?

    Amdahl's Law is a formula used to find the maximum theoretical speedup you can get from parallelizing a program. It states that the speedup is limited by the sequential portion of the program. If P is the proportion of the program that can be made parallel, and N is the number of processors, the maximum speedup is 1 / ((1 - P) + (P / N)).

    The key takeaway is that even with infinite processors, the speedup is limited by the part of the code that must run sequentially.

    114

    What is the producer-consumer pattern?

    It's a classic concurrency pattern that decouples the process of producing work (Producers) from the process of consuming that work (Consumers). The two are connected by a shared, thread-safe queue (usually a BlockingQueue).

    • Producers: Generate data or tasks and put them into the queue.
    • Consumers: Take data or tasks from the queue and process them.

    This pattern improves modularity and helps smooth out workloads where the rate of production and consumption might vary.

    115

    What is the readers-writers pattern?

    This pattern allows concurrent read access to an object but requires exclusive access for write operations. It's designed to improve performance in situations where a shared resource is read much more frequently than it is written to. ReadWriteLock is a direct implementation of this pattern.

    116

    What is a "work-stealing" algorithm?

    Work-stealing is used by the ForkJoinPool. Each worker thread has its own double-ended queue (deque) of tasks. When a thread finishes all tasks in its own deque, it looks at the deques of other busy threads and "steals" a task from the tail of their queue. This balances the load efficiently and reduces contention, as threads usually take work from the head of their own deque and steal from the tail of others'.

    117

    What is a memory barrier?

    A memory barrier (or memory fence) is a low-level instruction that forces the CPU to enforce an ordering constraint on memory operations. It ensures that operations before the barrier are completed before operations after the barrier are started. volatile writes and synchronized releases insert memory barriers to ensure visibility and ordering.

    118

    What is false sharing?

    False sharing is a performance-degrading issue in multi-core systems. It occurs when two threads on different cores modify variables that are located on the same cache line. A cache line is the smallest unit of memory that can be transferred between main memory and a CPU cache. Even though the threads are modifying different variables, the hardware's cache coherency protocol will invalidate the entire cache line for both cores every time one of them performs a write. This causes excessive cache invalidations and memory traffic.

    It can be mitigated by padding data structures to ensure that independent variables do not share a cache line.

    119

    How does `Thread.sleep(0)` work?

    Thread.sleep(0) is sometimes used as a hint to the operating system's thread scheduler to yield the CPU to another thread. It suggests that if there are other runnable threads, one of them should be scheduled to run. However, the exact behavior is OS-dependent and it's not a reliable way to manage thread execution. Thread.yield() is a more idiomatic, though still advisory, way to express this intent.

    120

    What is busy-spinning?

    Busy-spinning (or busy-waiting) is a technique where a thread repeatedly checks a condition in a tight loop without relinquishing the CPU (e.g., while (condition) {}). This consumes a lot of CPU cycles. It is only appropriate in rare, low-level situations where the wait time is expected to be extremely short and the cost of blocking and re-scheduling the thread is higher than the cost of the wasted CPU cycles.

    121

    Can a constructor be synchronized in Java?

    No. A constructor cannot be declared as synchronized. The reason is that only the thread creating the object has access to it during construction, so there is no need for synchronization. The synchronized keyword on a method synchronizes on the this object's lock, but this is not fully available to other threads until the constructor has finished.

    122

    What are the benefits of immutable objects in concurrency?

    An object is immutable if its state cannot be changed after it is created. Immutable objects are inherently thread-safe.

    • No Locks Needed: Since they cannot be modified, there's no need for locks to protect their state.
    • Safe to Share: They can be shared freely among threads without any risk of corruption.
    • No Inconsistent State: They are never in an inconsistent state.

    Examples include String, Integer, and the record classes in modern Java.

    123

    Scenario: You are designing a web crawler. How would you use concurrency to make it efficient?

    Traditional Approach (Platform Threads)

    1. ExecutorService: Use a fixed-size ExecutorService to manage a pool of worker threads. The size of the pool would be tuned based on whether the task is more I/O-bound (downloading pages) or CPU-bound (parsing HTML).
    2. Task Definition: Each task would be a Runnable or Callable that performs the following steps: a. Take a URL from a shared queue. b. Download the web page (I/O-bound). c. Parse the HTML to extract new links (CPU-bound). d. Put the newly found links into a shared queue or set to be processed.
    3. Shared Data Structures:
      • Use a BlockingQueue<URL> for the URLs to be crawled. This provides thread-safe access and back-pressure.
      • Use a ConcurrentHashMap<URL, Boolean> or a ConcurrentSkipListSet to keep track of visited URLs to avoid duplicate work and crawling loops.
    4. Coordination: A CountDownLatch or Phaser could be used to determine when the crawl is "complete" (e.g., when the queue is empty and all worker threads are idle).

    Modern Approach (Virtual Threads + Structured Concurrency)

    public class ModernWebCrawler { private final Set<URL> visitedUrls = ConcurrentHashMap.newKeySet(); private final BlockingQueue<URL> urlQueue = new LinkedBlockingQueue<>(); public void crawl(URL startUrl) throws InterruptedException { urlQueue.offer(startUrl); try (var scope = StructuredTaskScope.open()) { // Create multiple crawler tasks for (int i = 0; i < 100; i++) { // 100 virtual threads scope.fork(() -> crawlWorker()); } scope.join(); } } private void crawlWorker() { while (!urlQueue.isEmpty()) { URL url = urlQueue.poll(); if (url != null && visitedUrls.add(url)) { try { String content = downloadPage(url); List<URL> links = extractLinks(content); links.forEach(urlQueue::offer); } catch (Exception e) { // Handle error } } } } }

    Benefits of Modern Approach:

    • Higher Concurrency: Can handle thousands of concurrent downloads
    • Better Resource Utilization: Virtual threads are lightweight
    • Structured Error Handling: Automatic cancellation on failures
    • Simpler Code: No manual thread pool management
    124

    How do you handle exceptions in `CompletableFuture` chains?

    You can use the exceptionally() or handle() methods.

    • exceptionally(Function<Throwable, T>): This provides a way to recover from an exception. It's called only if the preceding stage completes exceptionally. It receives the exception and can return a default/fallback value.
    • handle(BiFunction<T, Throwable, U>): This is more general. It's called regardless of whether the preceding stage completed normally or exceptionally. It receives both the result (which will be null if an exception occurred) and the exception (which will be null if it completed normally). It allows you to transform the result in either case.
    125

    What is the `VarHandle` API?

    Introduced in Java 9, VarHandle is a replacement for the low-level, unsafe methods and provides a modern, safe, and efficient way to perform atomic and ordered operations on fields. It's a more powerful and general-purpose version of the Atomic* classes. It can be used to perform CAS operations, volatile reads/writes (memory-fenced operations), and other fine-grained memory ordering operations on object fields and array elements.

    126

    What is Structured Concurrency?

    Structured Concurrency, introduced as a preview feature in Java 21 and finalized in Java 25, is a programming paradigm that treats groups of related tasks running in different threads as a single unit of work. This approach streamlines error handling and cancellation while managing the lifecycle of concurrent operations cohesively.

    Key Benefits:

    • Short-circuit error handling: If any subtask fails, others are cancelled automatically
    • Automatic cancellation: If the parent thread is interrupted, all subtasks are cancelled
    • Clear structure: Fork subtasks, join them, then handle results or exceptions
    • Easy debugging: Thread dumps clearly show the task hierarchy

    Basic Usage:

    try (var scope = StructuredTaskScope.open()) { var task1 = scope.fork(() -> fetchUserData(userId)); var task2 = scope.fork(() -> fetchOrderHistory(userId)); var task3 = scope.fork(() -> fetchPreferences(userId)); scope.join(); UserData user = task1.get(); List<Order> orders = task2.get(); Preferences prefs = task3.get(); return buildUserProfile(user, orders, prefs); }

    Related: Learn more about Structured Concurrency in our comprehensive guide.

    127

    What are Scoped Values?

    Scoped Values, finalized in Java 25, provide a safer and more structured way of sharing data within a thread and its child threads compared to ThreadLocal variables. They address the limitations of ThreadLocal including unconstrained mutability and unbounded lifetime.

    Key Benefits:

    • Immutable: Values cannot be changed after being set
    • Automatic cleanup: Values are automatically cleared when the scope ends
    • Inheritance: Child threads created with Structured Concurrency inherit scoped values
    • Security: ScopedValue objects act as capabilities - only code with access can read values

    Basic Usage:

    public class UserContext { public static final ScopedValue<String> user = ScopedValue.newInstance(); } // Setting and using scoped values ScopedValue.where(UserContext.user, "alice").run(() -> { // User is "alice" in this scope processUserRequest(); }); // Safe access with default value String currentUser = UserContext.user.orElse("anonymous");

    Related: Learn more about Scoped Values in our detailed guide.

    128

    What are Stable Values?

    Stable Values, introduced as a preview feature in Java 25, provide a standardized way to lazily initialize constants with thread-safety and JVM optimizations. They solve the problems of traditional lazy initialization patterns like double-checked locking.

    Key Benefits:

    • Thread-safe lazy initialization: No manual synchronization needed
    • True immutability: Once set, values cannot be changed
    • JVM optimizations: Full constant folding and performance optimizations
    • Clean API: Minimal boilerplate code

    Basic Usage:

    public class DatabaseConfig { private static final StableValue<ConnectionPool> connectionPool = StableValue.of(); public static ConnectionPool getConnectionPool() { return connectionPool.orElseSet(() -> new ConnectionPool(loadDatabaseConfig())); } }

    Related: Learn more about Stable Values in our comprehensive guide.

    129

    What are virtual threads (Project Loom)?

    Virtual threads, a major feature in modern Java (preview in 19/20, final in 21), are extremely lightweight threads managed by the JVM, not the OS. A large number of virtual threads run on a small number of OS "carrier" threads.

    • Key Benefit: They allow you to write simple, synchronous-style, blocking code (e.g., InputStream.read()) that scales like asynchronous, non-blocking code. You can have millions of virtual threads running concurrently.
    • How it works: When a virtual thread executes a blocking I/O operation, it is "unmounted" from its carrier OS thread, and the carrier thread is freed up to run another virtual thread. Once the I/O operation is complete, the virtual thread is "remounted" on an available carrier thread to continue its execution.

    Important Note: In Java 24, virtual threads no longer get pinned in synchronized blocks, addressing a major limitation that previously required using ReentrantLock instead of synchronized for optimal performance.

    This dramatically simplifies writing highly concurrent server applications.

    Related: Learn more about Virtual Threads in our Spring Boot Concurrency Guide and Concurrency Limits and Modern Solutions.

    130

    Scenario: How would you improve the performance of a high-contention logging framework?

    A high-contention logger can become a bottleneck because multiple threads are trying to write to the same resource (a file or the console) through a synchronized block.

    1. Asynchronous Logging: The best approach is to make the logging asynchronous.
      • Application threads would not write directly to the disk. Instead, they would put log messages into a non-blocking or highly concurrent queue (like LinkedBlockingQueue or a specialized lock-free queue).
      • A single, dedicated background thread would be responsible for taking messages from this queue and writing them to the disk in batches.
    2. Benefits:
      • Decoupling: The application threads are decoupled from the slow I/O operation. They can quickly place the message in the queue and continue their work without blocking.
      • Batching: The background thread can write multiple log messages at once, which is much more efficient for disk I/O.
      • Reduced Contention: The only point of contention is the queue, and concurrent queues are highly optimized for this.

    This is the architecture used by modern high-performance logging frameworks like Log4j2 and Logback.

    131

    Modern Concurrency Patterns and Best Practices

    When to Use Different Concurrency Approaches

    ScenarioRecommended ApproachReason
    I/O-bound tasksVirtual ThreadsLightweight, perfect for blocking I/O operations
    CPU-intensive tasksFixed Thread Pool (size = CPU cores)Optimal resource utilization
    Task coordinationStructured ConcurrencyClear error handling and cancellation
    Data sharingScoped Values (Java 25+)Safer than ThreadLocal
    Lazy initializationStable Values (Java 25+)Thread-safe with JVM optimizations
    Simple async operationsCompletableFutureRich API for composition

    Common Concurrency Anti-patterns to Avoid

    1. Thread Pool Exhaustion: Creating too many threads or not properly managing thread pools
    2. Deadlock: Acquiring locks in different orders across threads
    3. Race Conditions: Accessing shared state without proper synchronization
    4. Memory Leaks: Not cleaning up ThreadLocal variables or not shutting down ExecutorServices
    5. False Sharing: Placing frequently accessed variables on the same cache line

    Related: Learn about modern concurrency solutions and overcoming traditional limitations in our Concurrency Limits and Modern Solutions guide.

    132

    Performance Considerations and Monitoring

    Thread Pool Sizing Guidelines

    Task TypeRecommended Pool SizeReasoning
    CPU-boundNumber of CPU coresAvoid context switching overhead
    I/O-bound2-4x CPU coresAllow for blocking operations
    Mixed workload1.5-2x CPU coresBalance between CPU and I/O
    Virtual threadsThousands to millionsLightweight, perfect for I/O

    Monitoring Concurrency Issues

    1. Thread Dumps: Use jstack or jcmd to analyze thread states
    2. JVM Metrics: Monitor thread count, deadlocks, and contention
    3. Profiling Tools: Use JProfiler, VisualVM, or async-profiler
    4. Application Metrics: Track task completion times and queue sizes

    Common Performance Pitfalls

    • Over-threading: Creating more threads than necessary
    • Under-threading: Not utilizing available CPU cores
    • Lock Contention: Too many threads competing for the same locks
    • Memory Leaks: Not cleaning up thread-local variables
    • False Sharing: Variables on the same cache line causing invalidations

    Best Practices Summary

    1. Use Virtual Threads for I/O-bound applications (Java 21+)
    2. Prefer Structured Concurrency for task coordination (Java 25+)
    3. Use Scoped Values instead of ThreadLocal for data sharing (Java 25+)
    4. Use Stable Values for lazy initialization (Java 25+)
    5. Profile before optimizing - measure actual performance bottlenecks
    6. Test under load - concurrency issues often only appear under stress

    For more in-depth tutorials and resources, check out my blog and social media:

    🔗 Blog: https://codewiz.info

    🔗 LinkedIn: https://www.linkedin.com/in/code-wiz-740370302/

    🔗 Medium: https://medium.com/@code.wizzard01

    🔗 Github: https://github.com/CodeWizzard01

    Summarise

    Transform Your Learning

    Get instant AI-powered summaries of YouTube videos and websites. Save time while enhancing your learning experience.

    Instant video summaries
    Smart insights extraction
    Channel tracking