skip to content

Concurrency & Multithreading

Java's whole concurrency surface: threads, synchronized and volatile, explicit locks and synchronizers, atomics, executors and CompletableFuture, fork/join, concurrent collections, the classic hazards, and virtual threads. It is the deepest and most heavily weighted area of Java interviews above junior level.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

253 · 12 sections

What is the difference between calling start() and calling run() directly on a Thread?

level: juniorimportance: must knowfreq 88%
basics
~20 s

start() creates a new thread and runs your run() code on it, so it executes concurrently. Calling run() directly just runs the code on the current thread, like an ordinary method call — no new thread, no concurrency.

open as a page

What are the two main ways to define the work a thread runs in Java, and how do they differ?

level: juniorimportance: must knowfreq 78%
basics
~20 s

You can extend the Thread class and override its run() method, or implement the Runnable interface's run() method and hand it to a Thread. Both put your code in run(); Runnable just separates the task from the thread.

open as a page

What does it mean to interrupt a thread in Java, and what does calling interrupt() actually do?

level: juniorimportance: must knowfreq 70%
basics
~10 s

Calling interrupt() on a thread just sets a boolean flag on it asking it to stop. It does not forcibly kill the thread. The thread must check the flag and decide to stop itself.

open as a page

What does Thread.join() do, and when would you use it?

level: juniorimportance: must knowfreq 72%
basics
~10 s

Calling t.join() makes the current thread wait until thread t finishes before continuing. You use it to make sure a worker thread's work is done before reading its results.

open as a page

What does Thread.sleep() do, and how does it differ from Object.wait() regarding locks?

level: juniorimportance: must knowfreq 78%
basics
~10 s

Thread.sleep() pauses the current thread for a given time but keeps any locks it holds. wait() also pauses the thread but releases the lock on the object so other threads can use it.

open as a page

What is lock contention in a multithreaded Java program, and why does it hurt performance?

level: juniorimportance: must knowfreq 72%
basics
~20 s

Lock contention happens when several threads try to acquire the same lock at the same time. Only one can hold it, so the others wait. The more threads compete, the more time is spent waiting instead of doing real work, so the program slows down.

open as a page

What is the difference between a synchronized method and a synchronized block in Java, and when would you prefer one over the other?

level: juniorimportance: must knowfreq 78%
basics
~20 s

Both let only one thread run the protected code at a time. A synchronized method locks the whole method on the instance (or the class for static methods). A synchronized block locks only a chosen region and a lock object you pick, so it can be narrower.

open as a page

A boolean flag is updated by a writer inside a synchronized block but read by a worker loop without synchronization. Why might the worker never see the update, and how do the synchronized memory effects explain the fix?

level: juniorimportance: must knowfreq 64%
basics
~20 s

The reader can keep using a stale cached copy of the flag because nothing forces it to refresh from main memory. The fix is to read the flag with the same synchronization (same lock, or make it volatile) so entering re-reads the latest value the writer flushed.

open as a page

How does narrowing a critical section reduce contention, and what work should you move out of a lock?

level: middleimportance: must knowfreq 64%
basics
~20 s

Hold the lock for as short a time as possible. Only the steps that actually touch shared data need the lock. Move slow or unrelated work — I/O, network calls, logging, big computations, object creation — outside the locked block so other threads wait less.

open as a page

What is an intrinsic lock (monitor) in Java, and how does synchronized use it?

level: middleimportance: must knowfreq 70%
basics
~20 s

Every Java object has one built-in lock called its intrinsic lock or monitor. The synchronized keyword acquires that lock when a thread enters the region and releases it when the thread leaves, so only one thread at a time can hold it.

open as a page

You have a worker thread looping on a non-volatile boolean flag that another thread sets to false, but it never stops. Why, and how do you fix it?

level: juniorimportance: must knowfreq 70%
basics
~10 s

The worker may keep reading a cached copy of the flag and never see the update. Declare the flag volatile so every read sees the latest written value, and the loop will exit.

open as a page

What does the volatile keyword do in Java, and what problem does it solve?

level: middleimportance: must knowfreq 80%
basics
~20 s

Marking a field volatile guarantees that a thread reading it always sees the most recent value written by any other thread. It fixes the visibility problem where one thread's update can otherwise go unseen by another.

open as a page

When would you choose volatile over synchronized or an Atomic class, and when is volatile insufficient?

level: middleimportance: must knowfreq 72%
basics
~20 s

Use volatile when you only need a single field's latest value to be visible across threads and you don't combine reads and writes. Use synchronized or an Atomic class when you need atomic compound updates (like count++) or to coordinate several fields together.

open as a page

Why is double-checked locking broken in Java without a volatile field? Explain the unsafe-publication / reordering failure concretely.

level: seniorimportance: must knowfreq 80%
basics
~20 s

Without volatile, the line instance = new T() can be reordered so the reference is set before the object's fields finish initializing. Another thread doing the lock-free first check can then see a non-null reference pointing at a half-built object and use it.

open as a page

What is lazy initialization for a singleton, and why might a naive synchronized getter become a performance concern under high contention?

level: juniorimportance: should knowfreq 55%
basics
~20 s

Lazy initialization means you only create an object the first time it is actually needed, not at startup. Putting synchronized on the whole getter makes it thread-safe but forces every caller to wait for the lock even after the object already exists.

open as a page

What do wait(), notify(), and notifyAll() do, and what precondition must hold whenever you call any of them?

level: middleimportance: must knowfreq 78%
basics
~20 s

wait() makes the current thread pause and release the object's lock until another thread calls notify() or notifyAll() on the same object. notify() wakes one waiting thread; notifyAll() wakes them all. You must hold that object's lock (be inside a synchronized block on it) when you call any of the three, or you get IllegalMonitorStateException.

open as a page

What is a spurious wakeup, and what coding discipline does it force on every use of wait()?

level: middleimportance: must knowfreq 64%
basics
~20 s

A spurious wakeup is when a thread returns from wait() even though nobody called notify() or notifyAll(). Because it can happen, you must always call wait() inside a while loop that re-checks the condition, so the thread only proceeds when the condition is actually true — never inside a plain if.

open as a page

Implement a bounded producer-consumer buffer using wait/notify. Why does wait() have to release the lock for this to work?

level: seniorimportance: must knowfreq 70%
basics
~20 s

A producer adds items and a consumer removes them, sharing a fixed-size buffer guarded by one lock. The producer waits when the buffer is full; the consumer waits when it's empty; each notifies the other after changing the buffer. wait() must release the lock so the other thread can enter the synchronized region and make progress.

open as a page

When is it safe to use notify() instead of notifyAll(), and what bug appears if you pick wrong?

level: seniorimportance: should knowfreq 58%
basics
~20 s

notify() wakes one waiting thread; notifyAll() wakes all of them. notify() is only safe when every waiting thread is waiting for the exact same condition and any of them can proceed interchangeably. If different threads wait for different conditions, notify() might wake the 'wrong' one, which goes back to sleep, leaving the thread that could actually run never woken — the program stalls.

open as a page

How do Lock/Condition (await/signal/signalAll) improve on intrinsic wait/notify, and what does each map to?

level: principalimportance: should knowfreq 47%
basics
~20 s

ReentrantLock with Condition objects is the modern replacement for synchronized + wait/notify. await() maps to wait(), signal() to notify(), and signalAll() to notifyAll(). The big win is that one Lock can have several Conditions (several wait-sets), so you can wake exactly the right group of threads instead of waking everyone.

open as a page

What is a CountDownLatch and how do you use it?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A CountDownLatch is a one-time gate. You start it with a count. Threads call await() to wait; other code calls countDown() to lower the count. When it hits zero, every waiter is released and can continue.

open as a page

What is a Semaphore in Java and what problem does it solve?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A Semaphore holds a set number of permits. A thread calls acquire() to take a permit (waiting if none are free) and release() to give it back. It limits how many threads can use a resource at once.

open as a page

What is a CyclicBarrier and what is its barrier action?

level: middleimportance: must knowfreq 62%
basics
~20 s

A CyclicBarrier is a reusable meeting point for a fixed number of threads. Each thread calls await() and blocks until all of them arrive. Then they all proceed, and the barrier resets so it can be used again for the next round.

open as a page

What is a ReadWriteLock and when would you use one instead of a plain lock or synchronized?

level: middleimportance: must knowfreq 62%
basics
~20 s

A ReadWriteLock has two locks: a read lock many threads can hold at once, and a write lock only one thread can hold (with no readers at the same time). Use it when data is read far more often than written, so reads don't block each other.

open as a page

What does tryLock() do, and how do its non-blocking and timed forms help avoid deadlock?

level: middleimportance: must knowfreq 70%
basics
~20 s

tryLock() tries to grab the lock and returns true or false right away instead of waiting. tryLock(time, unit) waits only up to a time limit. This lets a thread give up instead of blocking forever, so threads can back off and avoid getting stuck in a deadlock.

open as a page

A teammate uses a `volatile int` and writes `count++` for a shared request counter, expecting it to be thread-safe. Is it? Explain and fix it.

level: juniorimportance: must knowfreq 70%
basics
~10 s

No. volatile makes the value visible across threads but count++ is read-then-add-then-write, three steps, so two threads can both read the same number and lose an update. Fix it with an AtomicInteger and incrementAndGet().

open as a page

What is the ABA problem in a CAS-based algorithm, and why can compare-and-swap miss it?

level: middleimportance: must knowfreq 62%
basics
~20 s

ABA is when a value goes A then B then back to A between a thread's read and its compare-and-swap. The CAS only checks the current value equals A, sees A, and succeeds, never noticing the change in between.

open as a page

What is compare-and-swap (CAS), and how do classes like AtomicInteger use it to update a value safely without locks?

level: middleimportance: must knowfreq 78%
basics
~20 s

CAS is an atomic operation that updates a value only if it still equals an expected old value. AtomicInteger uses it in a small retry loop, so threads can increment safely without taking a lock.

open as a page

What is LongAdder, and why might you prefer it over AtomicLong for a heavily-incremented counter?

level: middleimportance: should knowfreq 55%
basics
~20 s

LongAdder is a counter for many threads. Instead of one shared number, it keeps several internal cells so threads update different ones and rarely collide. You read the total with sum(). Under heavy concurrent writes it's faster than AtomicLong.

open as a page

Given AtomicInteger, atomic field updaters, and VarHandle, how do you decide which to use for low-level atomic field access?

level: middleimportance: should knowfreq 28%
basics
~20 s

Default to AtomicInteger/AtomicReference — they are simple and clear. Drop to an atomic field updater only on legacy code or to save memory across huge numbers of instances. For new low-level code that needs custom memory ordering or array-slot CAS, use VarHandle, the modern supported replacement for both updaters and sun.misc.Unsafe.

open as a page

What are the main factory methods on java.util.concurrent.Executors, and what kind of thread pool does each create?

level: juniorimportance: must knowfreq 70%
basics
~10 s

Executors is a helper class with static methods that build ready-made thread pools: newFixedThreadPool (fixed number of threads), newCachedThreadPool (grows/shrinks on demand), newSingleThreadExecutor (one thread), and newScheduledThreadPool (for delayed/repeating tasks).

open as a page

What is the Executor interface, and what problem does it solve compared to creating threads directly?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Executor is a tiny interface with one method, execute(Runnable). You hand it a task to run; it decides how and when to run it. This separates submitting work from creating threads, so you don't call new Thread(...).start() everywhere.

open as a page

What is the difference between Callable<V> and Runnable, and when would you choose one over the other?

level: juniorimportance: must knowfreq 78%
basics
~10 s

Runnable.run() returns nothing and can't throw checked exceptions. Callable<V>.call() returns a value of type V and can throw checked exceptions. Use Callable when the task produces a result you need back.

open as a page

How do getActiveCount(), getPoolSize(), and getLargestPoolSize() differ on a ThreadPoolExecutor, and what does each tell you about pool health?

level: juniorimportance: must knowfreq 62%
basics
~10 s

getPoolSize() is how many threads exist right now, getActiveCount() is how many are currently running a task, and getLargestPoolSize() is the highest pool size ever reached. Together they show how busy the pool is.

open as a page

What role does the work queue play in a Java ThreadPoolExecutor, and what is the default rejection behavior when work can no longer be accepted?

level: juniorimportance: must knowfreq 62%
basics
~20 s

The work queue holds tasks that have been submitted but no thread is free to run yet. When the pool and queue are both full and no more work can be taken, the executor rejects new tasks; by default it throws RejectedExecutionException (AbortPolicy).

open as a page

What does thenCombine do on a CompletableFuture, and when would you use it?

level: juniorimportance: must knowfreq 70%
basics
~10 s

thenCombine waits for two independent CompletableFutures to both finish, then merges their two results into one new value using a function you supply.

open as a page

How do you manually drive a CompletableFuture to completion, and what is the difference between complete(value) and completeExceptionally(throwable)?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A CompletableFuture isn't always finished automatically — you can finish it yourself. complete(value) makes it succeed with that value; completeExceptionally(ex) makes it fail with that exception. Both return true only if your call was the one that completed it.

open as a page

What are the main ways to create a CompletableFuture that runs a task asynchronously, and how do supplyAsync and runAsync differ?

level: juniorimportance: must knowfreq 70%
basics
~10 s

Use CompletableFuture.supplyAsync when the task returns a value, and runAsync when it returns nothing. supplyAsync takes a Supplier and gives a CompletableFuture<T>; runAsync takes a Runnable and gives a CompletableFuture<Void>.

open as a page

What does thenApply do on a CompletableFuture, and how does it differ from thenAccept and thenRun?

level: juniorimportance: must knowfreq 70%
basics
~10 s

thenApply transforms the result into a new value (map). thenAccept consumes the result but returns nothing. thenRun runs an action that ignores the result entirely. All three run after the stage completes.

open as a page

In CompletableFuture, what is the difference between thenApply and thenApplyAsync, and which thread runs the callback in each case?

level: middleimportance: must knowfreq 70%
basics
~10 s

thenApply may run the callback on whatever thread completed the previous stage (or the caller). thenApplyAsync hands the callback to a separate thread pool to run, so it does not block the completing thread.

open as a page

In Java's Fork/Join framework, what is the difference between RecursiveTask and RecursiveAction, and when would you use each?

level: juniorimportance: must knowfreq 55%
basics
~20 s

RecursiveTask returns a result from its compute() method; RecursiveAction does not (it returns nothing). Use RecursiveTask when you need a value back (like a sum), and RecursiveAction when the work just has a side effect (like sorting an array in place).

open as a page

Explain the fork()/join() pattern in Fork/Join tasks. Why is the idiomatic ordering fork-one, compute-the-other, then join — and what goes wrong if you fork both then join both, or fork-then-immediately-join?

level: middleimportance: must knowfreq 60%
basics
~20 s

fork() schedules a subtask to run asynchronously; join() waits for it and returns its result. The idiom is: fork the left subtask, run the right one yourself by calling compute() directly, then join the left. This keeps the current thread busy instead of idle, and avoids forking a task only to immediately wait on it (which gives no parallelism).

open as a page

What is the ForkJoinPool common pool, who shares it, and why does blocking work on it cause problems?

level: seniorimportance: must knowfreq 62%
basics
~20 s

The common pool is a single ForkJoinPool the JVM shares process-wide. Parallel streams and CompletableFuture's default async methods all run on it. It is sized to the number of CPU cores minus one, so if you run blocking tasks on it, you starve every other feature that also uses it.

open as a page

How does ForkJoinPool's work-stealing algorithm work, and what is the role of the per-worker deques?

level: seniorimportance: must knowfreq 68%
basics
~20 s

Each worker thread has its own double-ended queue (deque) of tasks. A worker pushes and pops its own tasks from one end. When a worker runs out of work, it steals a task from the opposite end of another busy worker's deque, so idle threads stay busy and load stays balanced.

open as a page

What is the Fork/Join framework and ForkJoinPool, and what kind of workload is it designed for?

level: juniorimportance: should knowfreq 55%
basics
~20 s

ForkJoinPool is a thread pool for divide-and-conquer work. You fork a big task into smaller subtasks that run in parallel, then join their results back together. It is built for CPU-bound recursive splitting, like processing halves of an array.

open as a page

What is a BlockingQueue and what problem does it solve compared to a regular queue?

level: juniorimportance: must knowfreq 78%
basics
~20 s

A BlockingQueue is a thread-safe queue where a thread that takes from an empty queue waits until an item arrives, and (if bounded) a thread that adds to a full queue waits until space frees up. It makes producer-consumer hand-off safe and simple.

open as a page

What is ConcurrentHashMap, and when would you choose it over a HashMap or a Collections.synchronizedMap?

level: juniorimportance: must knowfreq 80%
basics
~20 s

ConcurrentHashMap is a thread-safe map that many threads can read and write at once safely. Use it instead of HashMap (not thread-safe) or a synchronized map (locks the whole map) when several threads share a map.

open as a page

Compare ArrayBlockingQueue and LinkedBlockingQueue. When would you choose each?

level: middleimportance: must knowfreq 72%
basics
~20 s

ArrayBlockingQueue is backed by a fixed-size array and is always bounded. LinkedBlockingQueue uses linked nodes and is optionally bounded (unbounded by default). ArrayBlockingQueue uses one lock; LinkedBlockingQueue uses two (put and take), so it often has higher throughput under contention.

open as a page

Given the put/take/offer/poll family, how do you choose the right operation for a producer-consumer and handle interruption and timeouts?

level: middleimportance: must knowfreq 60%
basics
~20 s

Use put/take when blocking forever is acceptable, offer/poll when you must act immediately and not wait, and the timed offer/poll when you'll wait but only up to a limit. put and take throw InterruptedException, so handle interruption (usually by restoring the interrupt flag and stopping).

open as a page

What are the atomic compound methods on ConcurrentHashMap (putIfAbsent, computeIfAbsent, compute, merge), and why use them instead of get-then-put?

level: middleimportance: must knowfreq 75%
basics
~20 s

They do a check-and-update as one atomic step, so two threads can't interleave between checking and writing. A manual get-then-put has a race window where both threads see the same state and clobber each other; these methods close that window.

open as a page

What is a deadlock in a multithreaded Java program, and can you give a concrete example of how it happens?

level: juniorimportance: must knowfreq 78%
basics
~20 s

A deadlock is when two or more threads each hold a lock the other needs, so all of them wait forever and none can make progress. Example: thread A holds lock 1 and waits for lock 2, while thread B holds lock 2 and waits for lock 1.

open as a page

What is thread confinement, and how does stack confinement of local variables make code thread-safe for free?

level: juniorimportance: must knowfreq 65%
basics
~20 s

Thread confinement means data is only ever touched by one thread, so no other thread can interfere and no locking is needed. Stack confinement is the special case where local variables live on one thread's stack and are never shared, making them automatically safe.

open as a page

Why are immutable objects inherently thread-safe, and what makes a Java class truly immutable?

level: juniorimportance: must knowfreq 78%
basics
~20 s

An immutable object never changes after it is built, so no thread can modify it while another reads it. With no shared mutable state to corrupt, no locking is needed. Make fields final, set them once in the constructor, and add no setters.

open as a page

What is a 'liveness failure' in concurrent programming, and how do deadlock, livelock, and starvation differ?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A liveness failure means threads never make progress. Deadlock: threads are blocked forever waiting on each other. Livelock: threads keep changing state reacting to each other but get nothing done. Starvation: one thread is perpetually denied a resource it needs.

open as a page

What is a race condition in Java, and why does it lead to nondeterministic, data-dependent bugs?

level: juniorimportance: must knowfreq 85%
basics
~20 s

A race condition is when two or more threads touch the same shared data at the same time without coordination, and the result depends on which thread happens to run first. Because timing varies, you get different, sometimes wrong, results each run.

open as a page

What is structured concurrency in Java, and what problem does it solve compared to launching threads or futures manually?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Structured concurrency ties a group of concurrent tasks to a code block. You open a scope, start subtasks, wait for them all, then close the scope. No task can outlive the block, so you never leak forgotten threads.

open as a page

What are the different ways to create and start a virtual thread in Java, and how do they differ?

level: juniorimportance: must knowfreq 72%
basics
~10 s

Use Thread.startVirtualThread(runnable) to create and start one immediately, Thread.ofVirtual().start(runnable) for the same via a builder, .unstarted(runnable) to build without starting, or Executors.newVirtualThreadPerTaskExecutor() to get one virtual thread per submitted task.

open as a page

Why is ScopedValue preferred over ThreadLocal, especially with virtual threads?

level: middleimportance: must knowfreq 55%
basics
~20 s

ThreadLocal data is mutable and lives until you manually remove it, which is easy to forget and wastes memory — bad when you have millions of virtual threads. ScopedValue is immutable, has a clear bounded lifetime, and cleans itself up automatically, so it is cheaper and safer.

open as a page

Walk through the lifecycle of a StructuredTaskScope: what do fork(), join(), Subtask.get(), and close() each do, and in what order must they be called?

level: middleimportance: must knowfreq 62%
basics
~20 s

Open the scope (usually in try-with-resources). fork() starts each subtask and returns a handle without blocking. join() waits for all subtasks to finish. After join, get() reads a successful subtask's result. close() (automatic) shuts the scope and waits for everything to stop.

open as a page

What happens when a virtual thread executes a blocking call such as Thread.sleep or blocking I/O?

level: middleimportance: must knowfreq 80%
basics
~20 s

The virtual thread pauses and unmounts from its carrier (the platform thread running it), freeing that carrier to run other virtual threads. When the blocking call is ready to continue, the virtual thread is remounted and resumes. The carrier is never wasted just waiting.

open as a page