Prev Next

Java / Java Multithreading

Last updated

Last updated 19 September 2026. Default examples are mid-level Spring and Java 17–21. Junior and senior sit in labeled sections so the first screen is not a fresher dump.

Java concurrency interviews start with happens-before and end with virtual threads. This hub is the multithreading head term. The Java 17/21 concurrency catalog is a sibling; Java 21 virtual threads have their own hub. Use this page for threads, locks, visibility, and executors.

Junior

A Thread runs a Runnable. start() creates the OS thread (or a virtual thread carrier setup). run() on the same object does not start a thread. sleep does not release a monitor. wait releases the monitor and must run inside synchronized. notify wakes one waiter; notifyAll wakes all. Prefer higher-level APIs.

synchronized is a reentrant monitor. static synchronized locks the Class object. volatile guarantees visibility of that field and establishes a happens-before on writes and subsequent reads. volatile is not atomic compound actions. i++ on a volatile int is still a race.

ExecutorService is how you run work after Java 5. newFixedThreadPool, newCachedThreadPool, and newSingleThreadExecutor. Always give a named factory and a bounded queue for production. Cached pools grow without a bound. shutDown versus shutDownNow. awaitTermination is not optional if you care about in-flight work.

InterruptedException is a signal. Restore the interrupt flag if you cannot honor it. Swallowing it is how services ignore cancellation.

Mid-level

Happens-before: unlock of a monitor happens-before a later lock of the same monitor. volatile write happens-before a later read. Thread.start happens-before the started thread's first action. Join happens-after the dying thread's last action. If you cannot name those, synchronized folklore will not save you.

java.util.concurrent: CountDownLatch (one-shot), CyclicBarrier (reusable), Semaphore, Phaser, CompletableFuture. Lock and ReentrantLock allow tryLock and interruptible lock. ReadWriteLock helps when reads dominate and the critical section is actually short. StampedLock is easier to misuse.

Deadlock: A waits for B while B waits for A. Detect with a thread dump. Prevent with lock ordering, tryLock timeouts, or fewer locks. Livelock and starvation are different; say how.

Thread confinement, immutability, and concurrent collections are the three default designs. Share less state. ConcurrentHashMap.compute is per-key atomic. A synchronized ArrayList is not a design.

CompletableFuture pipelines run on a default ForkJoinPool unless you pass an executor. Blocking in thenApply on the common pool saturates the JVM. thenApplyAsync with your executor is the mid-level fix.

Senior

JMM details: publication safety, final field freeze, and why an incorrectly published object can show default field values. Double-checked locking is correct only with volatile. Initialization-on-demand holder is still a valid lazy singleton.

Virtual threads (Java 21) are cheap concurrent tasks, not a replacement for CPU-bound thread pools. Blocking in virtual threads is expected. Pinning inside synchronized or native calls is the failure mode. Do not put a CPU-heavy loop on a million virtual threads and call it architecture.

Structured concurrency (preview / evolving) treats a set of tasks as one unit. Name the API version you used. ForkJoinPool.commonPool parallelism is Runtime.availableProcessors() minus one in many versions; that number is wrong for a container unless you set it.

Testing concurrency: JCStress is research-grade. For services, assert timeouts, use deterministic executors in unit tests, and fail on leftover threads. A flaky concurrency test is worse than none.

Probe yourself

sleep versus wait?

sleep keeps the monitor and wakes after a time. wait releases the monitor and waits for notify, notifyAll, or a timeout, inside synchronized.

Is volatile enough for i++?

No. Increment is read-modify-write. Use AtomicInteger or a lock.

Why pass an executor to thenApplyAsync?

The default pool is shared. Blocking work there starves other tasks in the JVM.

Related questions on this topic are linked below. Read the full answer on the question URL; this hub does not repeat those answers.

Pitfalls interviewers still use

Calling run() instead of start() is the classic junior miss. The code is correct and single-threaded. The interviewer watches you stare at a race that cannot happen.

Double-checked locking without volatile on the instance field is still assigned as a take-home. The reference can be visible before the constructor finishes. volatile or a holder class.

newCachedThreadPool() in a request handler under load creates thousands of platform threads and dies. Bounded pool, bounded queue, and a RejectedExecutionHandler you chose. Abort or CallerRuns, not silent discard, unless you meant to drop work.

Synchronizing on a String literal or Boolean.TRUE is a JVM-global lock. Synchronize on a private final Object you own.

ThreadLocal in a pool without remove() leaks the previous request's user into the next task. Virtual threads reduce the reuse problem for one-task-one-thread, but libraries that cache threads still leak.

For the room: write happens-before for a volatile flag that stops a loop. Then replace it with an interrupt. Then explain why a thread dump is your first production tool, not a new lock.

How to answer in the room

Start with happens-before, not with synchronized as a magic word. A monitor unlock happens-before a later lock of the same monitor. A volatile write happens-before a later read of that volatile. Thread.start happens-before the run of that thread. If you cannot say those three, do not jump to virtual threads.

run versus start: run executes on the caller. start schedules a thread. This still catches juniors. Mid-level is a race on a non-volatile flag. Senior is a broken double-checked lock or a thread pool that deadlocks on a same-pool inner submit.

ExecutorService: name the pool, the queue, and the rejection policy. Cached thread pool under a request handler is a production incident. Fixed pool plus an unbounded queue is a memory incident. Abort or CallerRuns unless you meant to drop work. Shut down with awaitTermination and a timeout, not with a daemon thread you hope the JVM kills.

Locks: synchronized for simple mutual exclusion you can see. ReentrantLock when you need tryLock or fairness. ReadWriteLock when reads dominate and you measured it. StampedLock is a specialist answer. Do not synchronize on a String literal.

ThreadLocal: useful for a request context on a platform thread. Poisonous in a pool if you forget remove. Virtual threads reduce reuse, not the need to think. Scope-local values are a later-Java story; date the version.

Interrupt: cooperative. sleep and wait throw InterruptedException; restore the interrupt flag if you cannot finish. A thread dump is the first production tool. jcmd Thread.print or an actuator endpoint. Do not add a new lock to 'fix' a hang you have not read.

Java 21: virtual threads for blocking I/O fan-out. Platform threads for CPU. Structured concurrency was preview in 21. Say that. Pinning inside synchronized is the migration tax. Measure before you flip Tomcat.

If they ask you to write a producer-consumer, start with a BlockingQueue and a bounded capacity. Do not start with wait/notify unless they demand it. wait/notify is how we used to do it; it is also how we missed a notify and hung. Name the poison-pill or interrupt strategy for shutdown. If they then ask about virtual threads, keep the bounded queue; unbounded fan-out plus a slow consumer is still a memory problem, carriers or not. Backpressure is the real answer. Say that out loud.

Interview questions

1. Consumer Producer problem.

package com.tutorials.threading; public class ConsumerProducerProblem { int value = 0 ; volatile boolean hasChanged = false ; public void produce( int value) throws InterruptedException { while (hasChanged == true ) { } System.out.println( "Value set:" + value); this.value = value;...

Read full answer

2. join() method.

An Instance method of a thread object. It pauses the current thread execution in which the statement is called until the thread object on which join method is invoked complete its execution....

Read full answer

3. Explain Exchanger in Java thread.

Exchanger is a synchronization point at which threads can pair and swap elements between the pair. Each thread presents some object on entry to the exchange method, matches with a partner thread, and receives its partner's object on return....

Read full answer

4. How do you create a Thread?

Implement Runnable interface and its only method run(), which will have the code to be executed by the thread. The object for the class that implements the Runnable interface is passed as an argument to the Thread constructor as explained...

Read full answer

5. What is CountDownLatch in Java?

CountDownLatch is a synchronizer type which allows one Thread to wait for one or more Threads before starts processing. A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads...

Read full answer

6. An example for join(long milliseconds).

package com.tutorials.threading; public class WaitForMSToJoinExample implements Runnable { public static void main(String[] args) throws InterruptedException { WaitForMSToJoinExample myObj = new WaitForMSToJoinExample(); Thread MyThreadObj = new Thread(myObj, "Child Thread" ); MyThreadObj.start()

Read full answer

7. Interrupts

indicates that the object has to stop doing what it does by calling interrupt() on the thread object. When it receives an interrupt, it returns from the run() method ....

Read full answer

8. Explain setUncaughtExceptionHandler method of Java Thread class.

The java.lang.Thread. setUncaughtExceptionHandler() method sets the handler to be invoked when this thread abruptly terminates due to an uncaught exception.

Read full answer

Next in this series: JDK, JRE, JVM, JIT
«
»

Comments & Discussions