$ 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