☕Java Developer Interview Questions — Fresher to 5 YOEJava Dev InterviewNEW
🧵
Multithreading & Concurrency
Threads, locks, executors, memory model · 30 questions
1
What is the difference between a process and a thread?
Must Know🌱 Fresher
A process is an independent running program with its own memory space (heap, code, OS resources) — processes don't share memory with each other by default and communication between them requires IPC (sockets, pipes, shared memory). A thread is a lightweight unit of execution within a process — all threads in the same process share the same heap and static memory, but each thread has its own stack and program counter. That's why threads are cheaper to create/switch than processes, but also why they need explicit synchronization to safely share data.
ThreadsBasics
2
What are the different ways to create a thread in Java?
Must Know🌱 Fresher
Extend Thread and override run() — simple, but you burn your one superclass slot and the task is tightly coupled to being a Thread.
Implement Runnable and pass it to new Thread(runnable) — preferred, since it separates "the task" from "the thing that runs it," and still lets your class extend something else.
Implement Callable<V> and submit it to an ExecutorService — like Runnable, but the task can return a value and throw checked exceptions, delivered via a Future<V>.
In modern code, none of these are used directly for application code — you submit Runnable/Callable tasks to an ExecutorService (see the ExecutorService question) rather than managing raw Thread objects.
Threads
3
What are the states in a thread’s lifecycle?
Must Know🌱 Fresher
🧵 Thread.State lifecycle
NEW --start()--> RUNNABLE <--scheduler--> (actually running on a CPU core)
| ^
lock unavailable | | lock acquired
v |
BLOCKED
RUNNABLE --wait()/join()/park()--> WAITING / TIMED_WAITING --notify()/timeout--> RUNNABLE
RUNNABLE --run() completes / exception--> TERMINATED
Threads
4
What happens if you call run() directly instead of start()?
Must Know🌱 Fresher
start() asks the JVM/OS to allocate a new call stack and actually schedule a new thread that then invokes run() on it. Calling run() directly is just an ordinary method call — it executes synchronously on the current thread, so you get sequential execution with none of the concurrency you asked for (and no new thread is ever created).
run() vs start()java
Thread t = newThread(() -> System.out.println(Thread.currentThread().getName()));
t.run(); // prints "main" -- runs on the calling thread, no new thread
t.start(); // prints "Thread-0" (or similar) -- actually runs concurrently
Threads
5
What does the `synchronized` keyword do, and what are its two forms?
Must Know🌱 Fresher
synchronized ensures only one thread can execute a critical section at a time by acquiring the associated object's intrinsic lock (monitor) before entering, and releasing it on exit (even via exception). Two forms:
Synchronized method — locks on this (instance methods) or the Class object (static methods). Simple, but locks the whole method body.
Synchronized block — synchronized(someObject) { ... } — lets you lock on a specific object and only around the code that actually needs protection, reducing contention.
What does the `volatile` keyword guarantee — and what does it NOT guarantee?
Must Know🌱 Fresher
volatile guarantees visibility — every read of a volatile variable goes to main memory (not a stale per-thread cache/register), and every write is flushed immediately, so other threads always see the latest value. It also prevents certain compiler/CPU instruction reordering around that variable.
What it does not give you: atomicity for compound operations. volatile int count; count++; is still read-modify-write across three steps — two threads can both read the same value before either writes back, losing an update, exactly like a non-volatile race condition.
What is the difference between `synchronized` and `volatile`?
Must Know📊 Mid
synchronized
volatile
Mutual exclusion
Yes — only one thread in the block at a time
No — doesn't lock anything
Visibility
Yes
Yes
Atomicity of compound ops
Yes (whole block is atomic)
No
Cost
Higher — can block threads
Lower — no blocking, just a memory barrier
Use for
Compound actions, critical sections
Simple flags/state where only visibility (not atomicity) matters, e.g. a `volatile boolean running` shutdown flag
SynchronizationVolatile
8
What is a deadlock, and how do you prevent it?
Must Know🌱 Fresher
A deadlock happens when two or more threads each hold a lock the other needs, and neither will release what it holds — so all of them wait forever. The classic cause is threads acquiring the same set of locks in different orders.
🔒 Classic lock-ordering deadlock
Thread 1: lock(A) --> waiting for lock(B)
Thread 2: lock(B) --> waiting for lock(A)
Thread 1 never releases A (waiting on B)
Thread 2 never releases B (waiting on A) --> deadlock, forever
Prevention: always acquire multiple locks in a fixed, consistent global order across all threads; use lock timeouts (tryLock(timeout)) so a thread can back off instead of waiting forever; or reduce lock scope/granularity so fewer locks are held simultaneously.
Deadlock
9
How do wait(), notify(), and notifyAll() work, and why must they be called inside a synchronized block?
Must Know📊 Mid
wait() releases the current thread's held monitor and parks it until another thread calls notify() (wakes one waiting thread) or notifyAll() (wakes all waiting threads) on the same object. They must be called from inside a synchronized block on that object because the JVM needs to atomically release the monitor as part of going to sleep — calling them without holding the lock throws IllegalMonitorStateException.
Standard wait/notify patternjava
synchronized (lock) {
while (!conditionMet) { // ALWAYS a while loop, never if -- guard against spurious wakeups
lock.wait();
}
// condition is now true, proceed
}
// elsewhere:synchronized (lock) {
conditionMet = true;
lock.notifyAll();
}
Synchronizationwait/notify
10
Why should you use ExecutorService instead of creating raw Threads?
Must Know🌱 Fresher
Creating a raw Thread per task is expensive (OS-level allocation, stack memory, scheduler registration) and gives you no control over how many run concurrently — spin up enough tasks and you'll exhaust memory or thrash the CPU with context switching. ExecutorService maintains a reusable pool of worker threads, queues submitted tasks, and gives you lifecycle management (shutdown()), result handling (Future), and back-pressure via bounded queues — all the bookkeeping raw threads leave to you.
What is the difference between ReentrantLock and `synchronized`?
Must Know📊 Mid
synchronized
ReentrantLock
Acquire/release
Implicit (block scope)
Explicit — lock() / unlock() (must use try/finally)
Try without blocking
Not possible
tryLock(), optionally with a timeout
Interruptible wait
No
lockInterruptibly()
Fairness option
No
Yes — new ReentrantLock(true) for FIFO waiting
Multiple condition queues
One (implicit wait/notify set)
Many, via newCondition()
Default to synchronized for simplicity; reach for ReentrantLock when you need tryLock/timeouts/fairness/multiple conditions. See the synchronized vs ReentrantLock deep dive →.
Locks
12
What is a race condition? Can you give an example?
Must Know🌱 Fresher
A race condition occurs when the correctness of a program depends on the unpredictable timing/interleaving of multiple threads accessing shared state — a classic example is two threads both executing counter++ on a shared, non-atomic int counter.
Lost update race conditionjava
int counter = 0;
// Thread A and Thread B both run: counter++;// counter++ is really: read counter, add 1, write back -- three steps, not atomic// Interleaving:// A reads 0// B reads 0// A writes 1// B writes 1 <- B's increment overwrote A's; final value is 1, should be 2
Fix: make the increment atomic — synchronized, an AtomicInteger, or a lock around the read-modify-write.
Concurrency
13
What is the difference between Runnable and Callable?
Important🌱 Fresher
Runnable
Callable<V>
Method
void run()
V call() throws Exception
Return value
No
Yes — via Future<V>
Checked exceptions
Can't declare any
Can throw and propagate them
Since
Java 1.0
Java 5 (with ExecutorService)
ThreadsCallable
14
What is a monitor (intrinsic lock) in Java?
Important📊 Mid
Every Java object has an implicit monitor (intrinsic lock) associated with it — that's what synchronized acquires and releases. Only one thread can hold a given object's monitor at a time; any other thread trying to enter a synchronized block on that same object blocks until it's released. It's reentrant — a thread already holding the lock can re-enter another synchronized block guarded by the same monitor without deadlocking itself (the JVM tracks a hold count).
Synchronization
15
What is the difference between deadlock, livelock, and starvation?
Important📊 Mid
Deadlock — threads are stuck, doing nothing, waiting on each other forever.
Livelock — threads are actively doing work (not blocked), but keep responding to each other in a way that prevents any of them from making real progress — e.g. two people repeatedly stepping aside for each other in a hallway, forever.
Starvation — a thread never gets CPU time or a lock because other threads (often higher priority, or just "greedier") keep getting scheduled/served ahead of it.
How would you implement the producer-consumer pattern in Java?
Important📊 Mid
Classic answer: a shared bounded buffer, producers wait() when it's full, consumers wait() when it's empty, and each side notifyAll() the other after changing the buffer's state. In modern code you almost never hand-roll this — BlockingQueue already implements exactly this pattern internally.
Modern approach — just use BlockingQueuejava
BlockingQueue<Task> queue = newLinkedBlockingQueue<>(100);
// Producer thread
queue.put(task); // blocks if full// Consumer threadTask t = queue.take(); // blocks if empty
ConcurrencyDesign Pattern
17
What thread pool types does the Executors factory provide, and when would you use each?
Important📊 Mid
Factory method
Behavior
Use when
newFixedThreadPool(n)
Fixed number of threads, unbounded queue
Known, stable concurrency level
newCachedThreadPool()
Grows unboundedly, reuses idle threads, reaps after 60s idle
Many short-lived, bursty tasks — risky for unbounded load
newSingleThreadExecutor()
Exactly one worker, tasks run strictly in order
Need sequential, ordered execution off the calling thread
newScheduledThreadPool(n)
Supports delayed/periodic execution
Cron-like or recurring background work
ExecutorServiceThread Pool
18
What is ThreadLocal, and when would you use it?
Important📊 Mid
ThreadLocal<T> gives each thread its own independent copy of a variable — reads/writes on one thread never affect another thread's copy, so no synchronization is needed. Common uses: per-request context (e.g. the current user or a request ID in a web app, propagated through code that doesn't want to thread a parameter through every method call), SimpleDateFormat instances (not thread-safe, so one per thread avoids sharing), and database connection/transaction binding.
Gotcha: with thread pools, threads are reused across requests — if you forget to remove() the value at the end, stale data can leak into the next task that happens to reuse that pooled thread.
ThreadLocal
19
How do Atomic classes like AtomicInteger achieve thread safety without locks?
Important📊 Mid
Classes like AtomicInteger use compare-and-swap (CAS), a hardware CPU instruction, instead of locks: "if the current value still equals what I last read, replace it with my new value — atomically, in one step." If another thread changed the value in between, the CAS fails and the atomic class simply retries the read-modify-write in a loop, without ever blocking a thread.
⚛️ AtomicInteger.incrementAndGet() (conceptually)
loop:
current = value // read
next = current + 1
if CAS(value, current, next) succeeds:
return next
else: // someone else updated value first
goto loop // retry with the fresh value
This is "lock-free" — no thread ever blocks waiting for another — which typically scales better than locking under high contention on a single value.
AtomicCAS
20
What is the difference between CountDownLatch and CyclicBarrier?
Important📊 Mid
CountDownLatch
CyclicBarrier
Reusable?
No — one-shot, count only goes down, never resets
Yes — automatically resets after each round of parties arrive
Who waits?
One or more threads wait for N events from (usually) other threads
N threads wait for each other to all arrive at the same point
Typical use
Main thread waits for N worker tasks to finish before proceeding
N worker threads all pause at a checkpoint until everyone catches up (e.g. simulation steps)
Concurrency Utilities
21
What is a Semaphore, and how does it differ from a lock?
Important📊 Mid
A Semaphore maintains a set of permits — acquire() blocks if no permits are available and takes one when they are; release() gives one back. Unlike a lock (which is binary — held or not, by exactly one thread), a semaphore can allow N concurrent holders, making it a natural fit for capping concurrent access to a limited resource pool (e.g. "at most 10 concurrent connections to this downstream service"). A semaphore with 1 permit behaves like a lock, except it isn't reentrant and — unlike synchronized/ReentrantLock — the thread that releases a permit doesn't have to be the one that acquired it, which is useful for signaling between different threads.
Semaphore
22
What is the difference between Future and CompletableFuture?
Important📊 Mid
Future<V> represents a pending async result, but its API is limited: you can only block on it (get()), poll (isDone()), or cancel — there's no way to attach a callback or chain further work without blocking a thread. CompletableFuture<V> (Java 8) implements Future but adds a full functional-style API: thenApply, thenCompose, thenCombine, exceptionally, letting you build a non-blocking pipeline of dependent async steps that only executes a callback once the upstream stage completes.
What is BlockingQueue, and what are its common implementations?
Important📊 Mid
BlockingQueue extends Queue with operations that block instead of failing/returning a sentinel when the queue can't service the call immediately: put() blocks while full, take() blocks while empty. This makes it the natural building block for producer-consumer pipelines and bounded work queues (which is exactly what backs ThreadPoolExecutor internally). Common implementations: ArrayBlockingQueue (fixed-capacity array), LinkedBlockingQueue (optionally unbounded, linked nodes), and PriorityBlockingQueue (unbounded, orders by priority instead of FIFO).
What is the difference between Collections.synchronizedList() and the java.util.concurrent collections?
Important📊 Mid
Collections.synchronizedList(list) wraps a plain list, synchronizing every individual method call on one shared lock — safe for single operations, but a compound action (like "check if empty, then add") still needs external synchronized around the whole sequence, and every access serializes through that one lock regardless of how many threads just want to read. The java.util.concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, concurrent queues) are purpose-built for concurrent access: finer-grained internal locking (or none at all for reads), atomic compound operations (computeIfAbsent, putIfAbsent), and fail-safe iteration — generally both faster and safer under real contention.
ConcurrencyCollections
25
How do you implement a thread-safe Singleton in Java?
Important📊 Mid
The simplest thread-safe approach is an enum singleton — the JVM guarantees enum instances are created exactly once, safely, with no extra code. The classic interview answer, though, is double-checked locking with a volatile field:
Double-checked locking singletonjava
classSingleton {
privatestaticvolatileSingleton instance; // volatile is essentialstaticSingleton getInstance() {
if (instance == null) { // 1st check, no locking (fast path)synchronized (Singleton.class) {
if (instance == null) { // 2nd check, now holding the lock
instance = newSingleton();
}
}
}
return instance;
}
}
Without volatile, the JIT/CPU could reorder the constructor's writes relative to the assignment of instance, letting another thread observe a non-null but partially-constructed object. An even simpler alternative that avoids all of this: the initialization-on-demand holder pattern, which leans on the JVM's own guaranteed-once-and-thread-safe class initialization instead of hand-rolled locking.
SingletonDesign Pattern
26
What is compare-and-swap (CAS), and what is the ABA problem?
Advanced📊 Mid
CAS only checks "is the value still equal to what I read?" — it can't detect that the value changed from A to B and back to A between your read and your swap. If your logic depended on the value never having changed (not just being equal now), you'd wrongly conclude nothing happened — that's the ABA problem. It matters most for CAS on references (e.g. lock-free stack/queue nodes), where a memory location can be freed and reallocated to a new object at the same address in between reads.
Mitigation: attach a version/stamp that always increases alongside the value (AtomicStampedReference), so "same value" can be distinguished from "same value, but it changed and changed back."
CASLock-Free
27
What is the Java Memory Model and the "happens-before" relationship?
Advanced⭐ Experienced
The Java Memory Model (JMM) defines what guarantees a thread has about seeing another thread's writes — without it, the compiler, CPU, and cache hierarchy are all free to reorder or delay operations for performance, as long as a single thread can't observe its own reordering. Happens-before is the JMM's core ordering rule: if action A "happens-before" action B, then A's effects (including all writes to memory before it) are guaranteed visible to B.
Concrete happens-before edges you should know:
A write to a volatile field happens-before every subsequent read of that same field.
Unlocking a monitor happens-before a later thread locking that same monitor.
Thread.start() happens-before anything the started thread does; the last action of a thread happens-before another thread's Thread.join() returns.
Without one of these edges connecting two threads, there's no guarantee a write by one thread is ever visible to another — regardless of "real time" ordering.
Memory ModelJMM
28
What is a daemon thread?
Optional🌱 Fresher
A daemon thread is a background thread that doesn't keep the JVM alive — once every non-daemon ("user") thread has finished, the JVM exits immediately, even if daemon threads are still running (they're simply killed mid-execution, with no cleanup). Mark a thread with setDaemon(true) before start(); typical uses are housekeeping tasks like a garbage collector thread or a periodic cache-eviction thread that should never block the JVM from shutting down.
Threads
29
Does thread priority guarantee scheduling order?
Optional🌱 Fresher
No — thread priority (Thread.setPriority, 1-10) is only a hint to the OS scheduler about relative importance; the actual scheduling behavior is entirely platform-dependent, and most modern OS schedulers make only a weak effort to honor it (some may ignore it almost entirely). You should never rely on priority for correctness — use proper synchronization/coordination primitives instead of hoping a "high priority" thread always wins a race.
Threads
30
What is context switching, and why can too many threads hurt performance?
Optional📊 Mid
A context switch is the OS saving one thread's execution state (registers, program counter, stack pointer) and loading another's so it can run on the CPU — necessary because there are usually far more runnable threads than CPU cores. Each switch has real cost (saving/restoring state, and often invalidating CPU caches/TLB entries that were "warm" for the previous thread). Spawning far more threads than you have cores to run them means the CPU spends an increasing share of time switching between threads rather than doing useful work — one of the reasons bounded thread pools (and, more recently, virtual threads for I/O-bound work) exist instead of "just spawn a thread per task."