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
| NEW | Thread object created, start() not yet called |
| RUNNABLE | Eligible to run — moves here after start() |
| RUNNING | CPU is actively executing this thread |
| BLOCKED | Waiting to acquire a monitor lock held by another thread |
| WAITING | Waiting indefinitely — wait(), join() w/o timeout |
| TIMED_WAITING | Waiting for a fixed time — sleep(), wait(ms), join(ms) |
| TERMINATED | Run completed (normally or via exception) |
Thread Creation Options
| extends Thread | Simplest, but no multiple inheritance — avoid in practice |
| implements Runnable | Preferred — 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 |
| CompletableFuture | Non-blocking, chainable, combinable async pipeline |
Runnable vs Callable
| Runnable | Callable |
| No return value | Returns a value (V) |
| No checked exceptions | Can 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 Section | Code block that touches shared state; must run exclusively |
| synchronized method | Locks this (instance monitor) for whole method |
| synchronized block | synchronized(obj){...} — locks only critical section |
| static synchronized | Locks the Class object — one lock across all instances |
| Monitor / Intrinsic Lock | Built-in lock every Java object has; backbone of synchronized |
Visibility & Atomicity
| volatile | Guarantees visibility of latest value across threads — NOT atomicity |
| AtomicInteger/Long | Lock-free, thread-safe counters via incrementAndGet() etc. |
| AtomicReference | Atomic updates to object references |
| CAS | Compare-And-Swap: read → compare expected → update or retry (lock-free) |
volatile vs AtomicInteger
| volatile | AtomicInteger |
| Visibility only | Visibility + Atomicity |
| No CAS | Uses CAS internally |
| Unsafe for count++ | Safe for count++ |
Locking Mechanisms & Deadlock
Locking Mechanisms
| ReentrantLock | Explicit lock — supports tryLock(), fairness, interruptibility |
| ReadWriteLock | Multiple readers OR one writer — great for read-heavy caches |
| StampedLock | Adds optimistic read mode — faster than ReadWriteLock |
| Fair Lock | new ReentrantLock(true) — grants lock in request order |
| tryLock() | Attempts lock, returns immediately/with timeout instead of blocking |
Deadlock, Starvation, Livelock
| Deadlock | Threads hold-and-wait on each other's locks in a cycle → all stuck forever |
| Starvation | A thread never gets CPU/lock because others keep getting priority |
| Livelock | Threads keep responding to each other but make no real progress |
| Prevention | Consistent 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
| Executor | Base interface — just execute(Runnable) |
| ExecutorService | Adds submit(), shutdown(), returns Future |
| ScheduledExecutorService | Run tasks after delay / periodically |
| FixedThreadPool | Fixed N threads, unbounded queue |
| CachedThreadPool | Grows/shrinks dynamically — risky under heavy load |
| SingleThreadExecutor | One thread, tasks run sequentially in order |
| ScheduledThreadPool | Fixed 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
| ConcurrentHashMap | Segmented/bucket-level locking — thread-safe map, no full-map lock |
| CopyOnWriteArrayList | Copies array on write — best when reads >> writes |
| ConcurrentLinkedQueue | Lock-free, non-blocking FIFO queue |
| BlockingQueue | Blocks on put()/take() when full/empty — Producer-Consumer |
| ConcurrentSkipListMap | Thread-safe sorted map, concurrent version of TreeMap |
Synchronization Utilities
| CountDownLatch | One-time — main thread awaits N workers finishing (not reusable) |
| CyclicBarrier | Reusable — all threads wait for each other at a checkpoint |
| Semaphore | Limits N concurrent threads — DB pool, rate limiting |
| Phaser | Advanced reusable barrier with dynamic party registration |
| Exchanger | Two 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
| ForkJoinPool | Divide-and-conquer pool with work-stealing between idle threads |
| RecursiveTask<V> | Fork-join task that returns a result |
| RecursiveAction | Fork-join task with no return value |
| Work Stealing | Idle 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 |
| InheritableThreadLocal | Child 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
| synchronized | ReentrantLock |
| JVM managed, auto-release | Manual lock/unlock (try/finally) |
| No tryLock() | tryLock() with timeout |
| Less flexible | Fair locking, interruptible |
| wait() | sleep() |
| Releases the lock | Keeps the lock |
| Object class | Thread class |
| Needs synchronized | No synchronized needed |
| HashMap | ConcurrentHashMap |
| Not thread-safe | Thread-safe, bucket-level locks |
| Race conditions possible | Safe concurrent access |
| CountDownLatch | CyclicBarrier |
| One-time use | Reusable |
| Main thread waits for workers | All 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