Java Concurrency
Java Concurrency
Process Pros
• Heavyweight
User Threads
• IPC is slower than in-memory communication.
Definition
Example
• Threads that perform core application work.
• Chrome tabs (often separate processes)
JVM Behavior
• JVM process itself
• JVM waits for all user threads to finish before exiting.
Thread
Use Cases
Definition
• Business logic
• A thread is a lightweight execution unit inside a process. • Request handling
• Threads share the same address space. • Batch jobs
Thread t = new Thread(() -> { Java merges “ready” and “running” into RUNNABLE
while (true) {
[Link]("Running..."); 3. BLOCKED
}
}); • Thread is waiting to acquire a monitor lock
[Link](true); • Happens with synchronized
[Link]();
synchronized(obj) {
Interview Trap Question // only one thread allowed
}
Q: Can daemon threads prevent JVM shutdown?
A: No. JVM exits when only daemon threads are running. 4. WAITING
“Threads are expensive because they require OS resources, stack memory, and introduce context-
switching overhead; that’s why thread pools are essential.”
Q. Difference Between BLOCKED vs WAITING Wakes up
Timeout
Automatically when lock free Only when notified
(unless timed waiting)
BLOCKED State
Q. Why Thread Pools Improve Performance
When it happens
Short Interview Answer
• Thread tries to enter a synchronized block
• Lock is already held by another thread Thread pools improve performance by reusing threads, reducing creation overhead, controlling
resource usage, and minimizing context switching.
Key Characteristics
ExecutorService pool = [Link](10); “More threads than CPU cores usually reduces throughput due to context switching and resource
contention.”
Q. What Happens When Too Many Threads Exist Q: Why not create one thread per request?
A: Thread creation cost, memory usage, and context switching make it unscalable.
3. Cache Thrashing • It prevents multiple threads from executing critical code simultaneously
• It ensures changes made by one thread are visible to others
• CPU caches constantly invalidated • It enforces ordering using the Java Memory Model
• Poor CPU efficiency
synchronized solves three core concurrency problems:
4. Lock Contention Increases 1. Race conditions
2. Visibility issues
• More threads competing for same locks
3. Instruction reordering issues
• BLOCKED time increases
5. Latency Gets Worse 2. What Does synchronized Actually Lock? (MOST IMPORTANT)
• More threads ≠ more performance synchronized always locks an OBJECT — never a method, block, or code.
• Requests wait longer
This is the #1 trap question in interviews.
Real-World Outcome
Correct understanding:
System becomes slower, unstable, and eventually fails under load.
• Java uses monitor locks
• Every object in Java has exactly one monitor }
• synchronized acquires that monitor }
“synchronized locks the monitor of an object. The object depends on how synchronization is • Two threads cannot execute synchronized instance methods on the same object
applied.” • Two threads can execute synchronized instance methods on different objects
• JVM and CPU cannot reorder instructions across: 13. Common Trap Questions
o Lock acquire
o Lock release ❓ Does synchronized lock the method or object?
This prevents:
Locks the object’s monitor
• Seeing partially constructed objects
• Broken double-checked locking ❓ Do two synchronized methods always block each other?
Explicit locks ([Link]) exist to provide more flexible and powerful • Higher throughput
locking than synchronized. • Better CPU utilization
ReentrantLock is a mutual exclusion lock with the same basic behavior as synchronized, but • Predictable
with extended capabilities. • Prevents starvation
Variants:
• tryLock() 9. AtomicInteger
• tryLock(timeout, unit)
Common methods:
• Avoid deadlock
• Timeout-based locking • get()
• Responsive systems • set()
• incrementAndGet()
6. Lock vs synchronized • compareAndSet()
Atomic operation:
7. When to Use Locks
if (value == expected)
Use when: value = newValue
Atomic classes provide lock-free, thread-safe operations on single variables using CAS. • Value: A → B → A
• AtomicStampedReference
• Versioning / stamps
12. Why Atomics Scale Better Under Contention 16. One-Page Interview Summary
Atomics avoid blocking and context switching. “Explicit locks like ReentrantLock provide more flexible synchronization than synchronized, including
fairness, timed lock acquisition, and interruptibility. Atomic classes use CAS to provide lock-free
Reasons: thread-safe operations on single variables. While atomics scale better under contention, they are
limited to simple state updates and do not replace locks for complex invariants.”
• No lock acquisition
• No thread suspension
• Threads retry instead of blocking 17. Mental Model
Compared to synchronized: synchronized → simple, blocking
ReentrantLock → flexible, blocking
• No monitor contention Atomic → lock-free, non-blocking
• No OS-level locking
Key insight:
18. Final Interview Tip
CAS spins; locks block.
If interviewer asks:
A thread pool is a managed set of reusable worker threads that execute submitted tasks.
5. Task Execution Flow (IMPORTANT)
Core idea:
When a task is submitted:
• Threads are created once
• Tasks are reused across threads 1. If active threads < corePoolSize → create new thread
• Limits parallelism 2. Else → enqueue task
3. If queue is full and threads < maxPoolSize → create new thread
4. Else → reject task
3. ThreadPoolExecutor (Foundation Class)
This explains everything about thread pool behavior.
All executor types are built on ThreadPoolExecutor.
Methods:
It can create unlimited threads under load.
Problems: • schedule()
• scheduleAtFixedRate()
• Thread explosion • scheduleWithFixedDelay()
• Context switching overhead
Use case:
• Memory exhaustion
• System instability • Heartbeats
• Cleanup tasks
Interview answer:
• Monitoring
“Cached thread pools are dangerous because they have an unbounded maximum thread size and
can overwhelm the system under heavy load.” 7. Queue Types (High-Level)
Internals: Risk:
• Memory leak
• One worker thread
• Unbounded queue
Bounded Queue (ArrayBlockingQueue)
• Fixed capacity
• Prevents overload 10. CPU-bound vs IO-bound Tasks
Best for:
CPU-bound Tasks
• Backpressure
• Production systems Spend most time using CPU
Examples:
SynchronousQueue
• Computation
• No capacity
• Encryption
• Task handed directly to thread
• Image processing
Used by:
Thread pool size:
• Cached thread pool
#threads ≈ number of CPU cores
8. Graceful Shutdown
Always shut down executors properly. IO-bound Tasks
Methods: Spend time waiting (DB, network, disk)
Why important:
1. What Is ForkJoinPool?
ForkJoinPool (FJP) is a specialized thread pool for recursive, divide-and-conquer tasks.
12. Executors vs ThreadPoolExecutor
It is the backbone of Java’s parallel streams and RecursiveTask / RecursiveAction.
13. Common Interview Traps FJP manages threads executing these subtasks efficiently.
Work Stealing
[Link]()
ForkJoinPool(
int parallelism, // # of worker threads • Tasks auto-split and scheduled efficiently
[Link] factory, • LIFO for local splits, FIFO for stolen splits
13. Best Practices • Hard to read & maintain
• Error propagation is messy
• CPU-bound tasks only → recursive, small, independent
CompletableFuture solves both.
• Avoid blocking tasks → or use managedBlocker
• Size parallelism ≈ #cores Interview line:
• Use CountedCompleter for fine-grained async control
• For I/O-heavy workloads → prefer virtual threads “CompletableFuture allows us to express async workflows declaratively without blocking threads.”
• Large tasks → threads idle CompletableFuture represents a future result that can be completed, transformed, or combined
• Blocking tasks → reduce throughput asynchronously.
• Recursive tasks too shallow → overhead may outweigh benefit
Key ideas:
• Shared mutable state → race conditions
• Non-blocking
15. One-Page Summary • Event-driven
• Functional composition
“ForkJoinPool is a highly optimized thread pool for CPU-bound, recursive tasks. Each worker thread • Works with Executors
has its own deque; LIFO execution improves cache locality, and idle threads steal tasks FIFO from
other threads. It scales efficiently without a central queue. Blocking tasks require managedBlocker. 3. Basic Creation
Parallel streams are built on ForkJoinPool.”
[Link](() -> fetchData());
[Link](() -> log());
Future CompletableFuture
.supplyAsync(() -> fetchUser())
• Blocking get() .thenApply(user -> enrich(user))
.thenApply(enriched -> save(enriched));
• No chaining
• Poor error handling No thread blocking. Each step triggers when the previous completes.
• Hard to combine multiple async tasks
Callbacks
• Callback hell
5. thenApply vs thenCompose (VERY IMPORTANT) exceptionally
CompletableFuture<User>
whenComplete
• thenAccept anyOf
• thenCompose
[Link](f1, f2);
7. Exception Handling
CompletableFuture provides structured error handling without try-catch. 9. Blocking Pitfalls (VERY IMPORTANT)
Calling get() or join() defeats the purpose of CompletableFuture.
Why blocking is bad:
13. When to Use CompletableFuture
• Wastes threads
Use when:
• Reduces scalability
• Can cause deadlocks (especially in commonPool) • Async workflows
• Non-blocking services
Bad:
• Parallel IO calls
[Link](); • Clean async composition
Avoid when:
Better:
• Simple synchronous logic
[Link](result -> process(result)); • CPU-bound tight loops
“I prefer async composition over blocking waits.” “CompletableFuture enables non-blocking asynchronous programming with composable pipelines.
It improves upon Future by allowing chaining, combining, and structured exception handling. Correct
use of thenApply vs thenCompose, proper executor selection, and avoidance of blocking calls are
10. Threading Model essential for scalable and maintainable async systems.”
• Default: [Link]
• Blocking tasks in commonPool are dangerous
• Use custom Executor for:
o IO Common Concurrency Problems
o Long-running tasks
1. Race Condition
11. CompletableFuture vs ExecutorService
Definition
Feature ExecutorService CompletableFuture
Blocking Often No A race condition occurs when multiple threads access shared mutable state concurrently, and
Composition the outcome depends on timing.
Error handling Poor Rich
Readability Medium High
Why it happens
• Lack of synchronization
12. Common Interview Traps • Non-atomic operations
• Shared mutable data
• thenApply vs thenCompose same
• get() is fine in async pipelines Example
• CompletableFuture creates threads
int count = 0;
• Async always means parallel
void increment() {
count++; // read → modify → write
} • Avoid nested locks
• Minimize shared state
Deadlock occurs when two or more threads wait forever for each other’s locks. Prevention
Thread A: lock1 → lock2 A thread is unable to gain access to resources because other threads dominate them.
Thread B: lock2 → lock1
Causes
Symptoms
• Unfair locks
• Threads stuck in BLOCKED • High-priority threads
• CPU usage low • Poor scheduling
• App appears “hung” • Long-held locks
Prevention Example
• Fair locks
Look for:
• Priority tuning
• Reduce lock hold time • "Found one Java-level deadlock"
• BLOCKED threads
5. False Sharing (Conceptual) • Locked monitors
False sharing occurs when independent variables used by different threads reside on the same Thread dump shows:
CPU cache line.
• Which thread holds which lock
Why it hurts • Which thread is waiting
You’ll see:
• Cache line invalidation
• Performance degradation Thread-1 waiting to lock <0x123>
• No correctness issue, only performance Thread-2 holding <0x123>
Symptoms
Step 3: Identify Lock Ordering Violation
• High CPU usage
• Poor scalability • Same locks acquired in different order
• No visible locking • Nested synchronized blocks
6. Debugging a Deadlock in Production (VERY IMPORTANT) 7. How to Detect Concurrency Issues Early
Step 1: Take Thread Dump • Code reviews
• Stress tests
jstack <pid>
• Load tests
or • Thread dump analysis
• Monitoring thread states
8. Common Interview Traps No. Spring is NOT thread-safe by default. Your code must be.
Definition
10. One-Page Interview Summary Singleton scope means one bean instance per Spring container.
“Concurrency problems arise from shared mutable state and improper coordination. Race conditions Why Spring chose singleton as default:
lead to incorrect results, deadlocks cause threads to block indefinitely, livelocks keep threads active
without progress, starvation prevents fair execution, and false sharing degrades performance due to • Memory efficiency
cache contention. In production, deadlocks are debugged using thread dumps and resolved by • Faster startup
improving lock ordering or using time-bounded locks.” • Shared configuration
• Aligns with stateless service design
11. Final Senior-Level Closing Line
Important clarification:
If interviewer asks:
Singleton ≠ thread-safe
“How do you think about concurrency bugs?”
Multiple threads can access the same singleton bean concurrently.
Answer:
“I focus on minimizing shared mutable state, using higher-level concurrency abstractions, and relying 3. Why Stateless Beans Are Thread-Safe
on immutability and structured async workflows where possible.”
Definition
@Service
1. Are Spring Beans Thread-Safe by Default? (TRAP QUESTION) class OrderService {
public int calculate(int price) {
Short answer (but senior-correct): return price * 2;
}
}
5. Common Real-World Mistakes
Interview line:
6. Request Scope vs Singleton Scope
“Stateless beans are naturally thread-safe because they don’t share mutable state.” Singleton (default)
or
What goes wrong:
@RequestScope
• Multiple threads modify total
• Race conditions Safe for:
• Inconsistent results
• Hard-to-debug production bugs • Request-specific data
• User context
Even if: • Temporary state
Never store request or user-specific data in singleton bean fields. 7. Mixing Scopes (Proxy Concept)
Problem:
• Singleton bean injecting request-scoped bean • Not stored in fields
Solution:
11. Interview-Ready Answer (Polished)
• Scoped proxies
“Spring beans are singleton by default for efficiency, but singleton does not imply thread-safe. Spring
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) does not synchronize access to beans. Stateless beans are safe because they don’t share mutable
state, while mutable fields in singleton beans can cause race conditions. Thread safety is the
developer’s responsibility, not Spring’s.”
Interview hint:
Spring uses proxies to safely inject shorter-lived beans into longer-lived ones. 12. Red Flags Interviewers Watch For
8. Thread Safety vs Bean Scope (IMPORTANT DISTINCTION) • Saying “Spring beans are thread-safe”
• Confusing scope with safety
Scope Thread Safety • Storing request data in fields
Singleton Not automatic • Overusing synchronization
Request Safe per request
Prototype Not safe automatically 13. One-Page Summary
Session Multiple threads possible
• Singleton = one instance, many threads
Scope controls lifecycle, not thread safety.
• Stateless = thread-safe
• Mutable fields = danger
9. How to Make Spring Beans Thread-Safe • Scope ≠ synchronization
• Spring manages lifecycle, not concurrency
Preferred approaches (in order):
Safe only because: ThreadLocal provides thread-confined storage: each thread has its own copy of a variable.
• Request data comes via method parameters ThreadLocal<UserContext> context = new ThreadLocal<>();
Each thread: This is why:
3. When ThreadLocal Is Appropriate (GOOD USE CASES) • Data leakage between requests
• Security vulnerabilities
✅ 1. Request / Context Data • Memory leaks
1. Opens transaction
5. ThreadLocal Memory Leak Problem 2. Binds it to thread
3. Executes method
Cause:
4. Commits/rolls back
• ThreadLocalMap uses weak references for keys 5. Unbinds transaction
• Values are strongly referenced
• Thread pools keep threads alive 8. Why Transactions Are NOT Thread-Safe
Solution: Key rule:
“Always remove ThreadLocal values in finally blocks.” 9. Transaction Propagation and Threads
Propagation works:
6. Spring’s Safer Alternatives
• Within the same thread
• Request scope beans • Across method calls
• Method parameters
• Context objects Propagation does NOT work:
• Framework-managed ThreadLocals
• Across new threads
• Across async boundaries
Example:
@Transactional
13. Interview-Ready Explanation
public void outer() {
new Thread(() -> inner()).start(); // NO TRANSACTION “Spring transactions are bound to the executing thread using ThreadLocal. They are not thread-safe
}
and cannot be shared across threads. Transactions guarantee database consistency, not in-memory
thread safety. Combining transactions with async execution requires explicit design.”
Common misunderstanding:
Key clarification:
counter++ is:
Code
1. Read
2. Increment
3. Write
Interview line:
Expected Result
Counter = 200000
Counter = 137421
Counter = 182344
•
2️⃣ Fix Using synchronized Atomicity + visibility
• Slower under contention
Result
Counter = 200000
What Changed
• Mutual exclusion
3️⃣ Fix Using AtomicInteger (Preferred)
✅ Task 1 Summary
Code
Version Correct Blocking Scales
Broken
synchronized
AtomicInteger
• Uses CAS
• Lock-free
• Better under contention
Interview comparison:
Code
Key Observations
[Link](5);
Observation:
Observation:
2️⃣ Observe Output (Pool Size = 2)
• Creates many threads
You’ll see: • Can spike CPU & memory
• Risk of OOM
Interview line:
“Cached thread pools are dangerous because they create unbounded threads.”
5️⃣ CPU-Bound vs IO-Bound Experiment 🎯 Final Interview Takeaways
CPU-Bound Task
• Race conditions are observable, not theoretical
• synchronized fixes correctness
• Atomics scale better
• Thread pools must be sized by workload
• Unbounded pools are dangerous
They allow you to write blocking-looking code that scales like async.
Interview one-liner:
“Virtual threads let us write simple synchronous code while achieving async-level scalability.”
Important: Now:
Blocking calls like sleep, socket, JDBC are virtual-thread-friendly. • JVM handles blocking efficiently
Senior answer:
“Virtual threads simplify concurrency, CompletableFuture still shines for async composition.”
8. When to Use Virtual Threads (IMPORTANT) synchronized(lock) {
[Link](); // pins carrier thread
}
✅ Use When:
Why dangerous:
• IO-bound workloads
• Web servers • Carrier thread blocked
• Microservices • JVM can’t reschedule
• Request-per-thread model • Scalability loss
• JDBC, REST calls, messaging
Solution:
Example:
• Avoid blocking inside synchronized
• Spring MVC • Use ReentrantLock instead
• REST APIs
• Batch jobs waiting on I/O 11. Virtual Threads & ThreadLocal
9. When NOT to Use Virtual Threads • Each virtual thread has its own ThreadLocal
• Millions of ThreadLocals = memory pressure
❌ CPU-bound tasks • Still must clean up
Senior note:
• Virtual threads don’t make CPU faster
• Still limited by cores “Virtual threads reduce thread cost, not ThreadLocal cost.”
A virtual thread gets pinned to a carrier thread if it enters a synchronized block during a blocking • Thread-bound
call. • Safe because virtual threads are threads
Example:
13. Virtual Threads ≠ Async Magic
Clarify in interview:
• They do NOT eliminate locks
• They do NOT fix race conditions
• They do NOT replace proper design
They improve:
• Scalability
• Simplicity
• Resource utilization