$ Java Multithreading
// Interview Prep — Quick Reference
Quick Reference
Basics
Process, Thread, Lifecycle, Creation
Safety
Race Condition, Sync, Volatile, Atomic, Locks
Coordination
wait/notify, Latch, Barrier, Semaphore
Modern Java
Executor, CompletableFuture, ForkJoin, Concurrent Collections
Legend
Core Concept
Sync / Safety
Executor / Async
Collections / Utilities
Danger Zone (Deadlock/Starvation)
Core Rules (Say These First In Interview)
Concurrency Multiple Tasks Make Progress in Overlapping Time (Not Necessarily Same Instant)
Parallelism Multiple Tasks Run Truly Simultaneously on Multiple Cores
Multithreading Multiple Threads Run Concurrently Within One Process, Sharing Memory
Thread Lifecycle & Creation
Thread Lifecycle
NEWThread object created, start() not yet called
RUNNABLEEligible to run — moves here after start()
RUNNINGCPU is actively executing this thread
BLOCKEDWaiting to acquire a monitor lock held by another thread
WAITINGWaiting indefinitely — wait(), join() w/o timeout
TIMED_WAITINGWaiting for a fixed time — sleep(), wait(ms), join(ms)
TERMINATEDRun completed (normally or via exception)
Thread Creation Options
extends ThreadSimplest, but no multiple inheritance — avoid in practice
implements RunnablePreferred — no return value, no checked exceptions
Callable<V>Returns a value, can throw checked exceptions
Future<V>Handle to result of async task — get() blocks
CompletableFutureNon-blocking, chainable, combinable async pipeline
Runnable vs Callable
RunnableCallable
No return valueReturns a value (V)
No checked exceptionsCan throw checked exceptions
run()call()
Thread Safety — Sync, Visibility, Atomicity
Race Condition & Synchronization
Race Condition≥2 threads read-modify-write same shared state concurrently → lost updates
Critical SectionCode block that touches shared state; must run exclusively
synchronized methodLocks this (instance monitor) for whole method
synchronized blocksynchronized(obj){...} — locks only critical section
static synchronizedLocks the Class object — one lock across all instances
Monitor / Intrinsic LockBuilt-in lock every Java object has; backbone of synchronized
Visibility & Atomicity
volatileGuarantees visibility of latest value across threads — NOT atomicity
AtomicInteger/LongLock-free, thread-safe counters via incrementAndGet() etc.
AtomicReferenceAtomic updates to object references
CASCompare-And-Swap: read → compare expected → update or retry (lock-free)
volatile vs AtomicInteger
volatileAtomicInteger
Visibility onlyVisibility + Atomicity
No CASUses CAS internally
Unsafe for count++Safe for count++
Locking Mechanisms & Deadlock
Locking Mechanisms
ReentrantLockExplicit lock — supports tryLock(), fairness, interruptibility
ReadWriteLockMultiple readers OR one writer — great for read-heavy caches
StampedLockAdds optimistic read mode — faster than ReadWriteLock
Fair Locknew ReentrantLock(true) — grants lock in request order
tryLock()Attempts lock, returns immediately/with timeout instead of blocking
Deadlock, Starvation, Livelock
DeadlockThreads hold-and-wait on each other's locks in a cycle → all stuck forever
StarvationA thread never gets CPU/lock because others keep getting priority
LivelockThreads keep responding to each other but make no real progress
PreventionConsistent lock ordering, tryLock() + timeout, avoid nested locks
Classic Deadlock Example
// Thread-1 lock(A); lock(B); // Thread-2 (reverse order = deadlock) lock(B); lock(A);
wait() / notify() / notifyAll()
wait()Releases lock, waits until notified — must be inside synchronized block
notify()Wakes ONE waiting thread (unspecified which)
notifyAll()Wakes ALL waiting threads
sleep()Pauses thread but KEEPS the lock (Thread class, not Object)
Executor Framework & Thread Pools
Executor Framework
ExecutorBase interface — just execute(Runnable)
ExecutorServiceAdds submit(), shutdown(), returns Future
ScheduledExecutorServiceRun tasks after delay / periodically
FixedThreadPoolFixed N threads, unbounded queue
CachedThreadPoolGrows/shrinks dynamically — risky under heavy load
SingleThreadExecutorOne thread, tasks run sequentially in order
ScheduledThreadPoolFixed pool for scheduled/repeating jobs
Production: ThreadPoolTaskExecutor
@Bean Executor taskExecutor() { ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor(); ex.setCorePoolSize(10); ex.setMaxPoolSize(20); ex.setQueueCapacity(100); ex.initialize(); return ex; }
Why Not new Thread()?
No pooling/reuse, no lifecycle mgmt, no backpressure/queueing, resource exhaustion risk in prod.
Thread Pool Sizing (Frequently Asked)
CPU-Bound Tasks
Threads = CPU Cores + 1
e.g. heavy computation, encryption, image processing
I/O-Bound Tasks
Threads = Cores × (1 + Wait/Compute)
e.g. DB calls, REST API calls, file I/O — usually need larger pools
Concurrent Collections & Synchronization Utilities
Concurrent Collections
ConcurrentHashMapSegmented/bucket-level locking — thread-safe map, no full-map lock
CopyOnWriteArrayListCopies array on write — best when reads >> writes
ConcurrentLinkedQueueLock-free, non-blocking FIFO queue
BlockingQueueBlocks on put()/take() when full/empty — Producer-Consumer
ConcurrentSkipListMapThread-safe sorted map, concurrent version of TreeMap
Synchronization Utilities
CountDownLatchOne-time — main thread awaits N workers finishing (not reusable)
CyclicBarrierReusable — all threads wait for each other at a checkpoint
SemaphoreLimits N concurrent threads — DB pool, rate limiting
PhaserAdvanced reusable barrier with dynamic party registration
ExchangerTwo threads swap data at a synchronization point
Fork-Join, Parallel Streams & CompletableFuture
CompletableFuture Chain (Aggregator Pattern)
supplyAsync()
thenApply()
thenCombine()
exceptionally()
Async Task → Transform → Combine Results → Handle Errors
Parallel API Calls
CompletableFuture<User> u = CompletableFuture.supplyAsync( () -> userService.getUser()); CompletableFuture<Order> o = CompletableFuture.supplyAsync( () -> orderService.getOrders()); CompletableFuture.allOf(u, o).join();
ForkJoinPool & Parallel Stream
ForkJoinPoolDivide-and-conquer pool with work-stealing between idle threads
RecursiveTask<V>Fork-join task that returns a result
RecursiveActionFork-join task with no return value
Work StealingIdle worker threads "steal" tasks from busy threads' queues
parallelStream()Uses common ForkJoinPool internally — good for CPU-bound work only
Avoid parallelStream() For
DB Calls
External APIs
Small Collections
ThreadLocal & Spring Boot Async
ThreadLocal
ThreadLocal<T>Per-thread isolated copy of a variable — no sharing across threads
InheritableThreadLocalChild threads inherit parent's ThreadLocal value
MDC (Logging)Uses ThreadLocal to carry correlation/request-ID through logs
Use Cases
User Context
Correlation ID
Request Tracking
Spring Boot @Async
@EnableAsync @Async public CompletableFuture<String> process() { return CompletableFuture .completedFuture("Done"); }
⚠ Will @Async Work In Same-Class Call?
NO — Spring AOP proxy is bypassed on self-invocation; the method must be called through the Spring-managed bean.
Kafka Consumer Concurrency
Partition → Consumer Assignment (Rule: Partitions ≥ Consumers)
@KafkaListener(topics = "orders", concurrency = "3") // 6 Partitions, 3 Consumers Consumer-1 → P0, P1 Consumer-2 → P2, P3 Consumer-3 → P4, P5
One Partition Is Consumed By Exactly One Consumer Within a Group — Extra Consumers Beyond Partition Count Sit Idle
Interview Comparisons
synchronizedReentrantLock
JVM managed, auto-releaseManual lock/unlock (try/finally)
No tryLock()tryLock() with timeout
Less flexibleFair locking, interruptible
wait()sleep()
Releases the lockKeeps the lock
Object classThread class
Needs synchronizedNo synchronized needed
HashMapConcurrentHashMap
Not thread-safeThread-safe, bucket-level locks
Race conditions possibleSafe concurrent access
CountDownLatchCyclicBarrier
One-time useReusable
Main thread waits for workersAll threads wait for each other
Must-Know Code Snippets
Runnable Task
class MyTask implements Runnable { public void run() { System.out.println("Running"); } }
Callable + Future
Callable<String> task = () -> "Success"; Future<String> f = executor.submit(task); String result = f.get(); // blocks
Synchronized Method
public synchronized void increment() { count++; }
Synchronized Block
synchronized(this) { count++; }
Volatile Flag (Safe Stop)
private volatile boolean running = true; // running = false; from another thread // visible immediately to the loop thread
AtomicInteger Counter
AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); count.getAndIncrement();
ReentrantLock with tryLock
Lock lock = new ReentrantLock(); if (lock.tryLock(5, TimeUnit.SECONDS)) { try { /* work */ } finally { lock.unlock(); } }
wait() / notifyAll()
synchronized(lock) { while (!ready) lock.wait(); // proceed after notifyAll() }
ExecutorService Basics
ExecutorService ex = Executors.newFixedThreadPool(10); ex.submit(task); ex.shutdown();
Producer-Consumer (BlockingQueue)
BlockingQueue<Integer> q = new ArrayBlockingQueue<>(10); q.put(1); // producer, blocks if full q.take(); // consumer, blocks if empty
CountDownLatch
CountDownLatch latch = new CountDownLatch(3); latch.countDown(); // per worker latch.await(); // main thread
Semaphore (Rate Limiting)
Semaphore sem = new Semaphore(5); sem.acquire(); try { /* limited resource */ } finally { sem.release(); }
ThreadLocal Context
ThreadLocal<String> user = new ThreadLocal<>(); user.set("Pranik"); user.get(); user.remove(); // avoid leaks in pools
CompletableFuture Chain
CompletableFuture.supplyAsync(this::callService) .thenApply(this::process) .exceptionally(ex -> "Fallback");
ConcurrentHashMap
ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>(); map.computeIfAbsent("k", k -> "v");
30-Second Interview Revision
Concurrency ≠ Parallelism (overlap vs simultaneous)
Thread: NEW→RUNNABLE→RUNNING→(BLOCKED/WAITING/TIMED_WAITING)→TERMINATED
Prefer Runnable/Callable over extending Thread
Race Condition = unsynchronized shared-state read-modify-write
volatile = visibility only, NOT atomicity
Atomic classes use CAS — lock-free thread safety
ReentrantLock > synchronized: tryLock(), fairness
Deadlock = circular wait on locks → prevent via lock ordering
wait() releases lock; sleep() keeps lock
Avoid new Thread() in enterprise code — use ExecutorService
CPU-bound pool size = cores + 1
I/O-bound pool size = cores × (1 + wait/compute)
parallelStream() uses common ForkJoinPool — avoid for I/O
CompletableFuture > Future: non-blocking, chainable
ConcurrentHashMap: bucket-level locks, no full-map lock
CopyOnWriteArrayList: best when reads >> writes
CountDownLatch = one-time; CyclicBarrier = reusable
Semaphore = limit N concurrent threads (DB pool, rate limit)
ThreadLocal = per-thread isolated data (context, correlation ID)
@Async fails on self-invocation — AOP proxy bypassed
Kafka: partitions ≥ consumers; extra consumers idle
Troubleshooting: thread dump for deadlock/blocking, heap dump for memory