0% found this document useful (0 votes)
3 views35 pages

Java Concurrency

The document provides a comprehensive overview of Java concurrency, focusing on the differences between processes and threads, their lifecycle, and the implications of context switching. It discusses the characteristics of user and daemon threads, the importance of thread pools for performance, and the use of synchronized blocks for mutual exclusion. Additionally, it covers explicit locks, such as ReentrantLock, and their advantages over synchronized methods.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views35 pages

Java Concurrency

The document provides a comprehensive overview of Java concurrency, focusing on the differences between processes and threads, their lifecycle, and the implications of context switching. It discusses the characteristics of user and daemon threads, the importance of thread pools for performance, and the use of synchronized blocks for mutual exclusion. Additionally, it covers explicit locks, such as ReentrantLock, and their advantages over synchronized methods.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

o Program counter

Java Concurrency • Shares:


o Heap
o Static variables
1. Process vs Thread o Open files

Process Pros

• Faster creation than processes


Definition
• Very fast communication (shared memory)
• A process is an independent execution unit with its own memory space.
Cons
• OS allocates separate virtual address space per process.
• Shared memory → race conditions
Key Characteristics
• One thread can corrupt shared state
• Has its own:
Example
o Heap
o Stack • Worker threads in a web server
o Program counter
• GC threads in JVM
o File descriptors (logically)
• Communication requires IPC (pipes, sockets, shared memory). Interview Sound Bite
Pros
“Processes provide isolation; threads provide concurrency. Processes are safer but heavier, threads
• Strong isolation → one process crash doesn’t affect others. are faster but require synchronization.”
• Better security boundaries.
2. User Threads vs Daemon Threads (Java Perspective)
Cons

• 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

Key Characteristics Daemon Threads


• Each thread has:
Definition
o Its own stack
• Background service threads that support user threads. ↓ run completes
TERMINATED
JVM Behavior

• JVM terminates automatically when only daemon threads remain.


Detailed Breakdown
Important Characteristics
1. NEW
• May be stopped abruptly
• finally blocks may not run • Thread object created
• Should not hold critical resources • start() not yet called

Use Cases Thread t = new Thread(runnable); // NEW

• Garbage Collector 2. RUNNABLE


• Monitoring
• Background cleanup • Thread is ready to run
• Heartbeats • May be:
o Actually executing on CPU
Code Example o Waiting for CPU time

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

• Thread waits indefinitely until another thread signals


3. Thread Lifecycle (Java)
Caused by
States Overview
• [Link]()
NEW • [Link]()
↓ start() • [Link]()
RUNNABLE
↓ waiting for lock 5. TIMED_WAITING
BLOCKED
↓ wait()/join() • Thread waits for specific time
WAITING
↓ sleep(timeout)/wait(timeout) Caused by
TIMED_WAITING
• [Link](ms) Interview Sound Bite
• wait(timeout)
• join(timeout) “Context switching is necessary for multitasking but excessive switching hurts performance due to
cache and CPU pipeline disruption.”
6. TERMINATED

• Thread execution finished


• Cannot be restarted 5. Why Thread Creation Is Expensive
Interview Tip 1. OS-Level Overhead

BLOCKED ≠ WAITING • Thread creation involves:


o Kernel data structures
• BLOCKED → waiting for lock
o Stack allocation
• WAITING → waiting for signal/condition
o Scheduling metadata

4. Context Switching (High Level) 2. Memory Allocation

What Is Context Switching? • Each thread needs:


o Stack memory (often ~1MB by default)
• OS switches CPU from one thread to another. • Thousands of threads → OutOfMemoryError
• Saves current thread state and restores another.
3. Context Switching Explosion
Saved State Includes
• More threads → more switching
• Program counter
• CPU spends more time managing threads than doing work
• Registers
• Stack pointer 4. Startup Latency
• CPU flags
• Thread startup is not instantaneous
Why Context Switching Happens • Hurts request latency in high-throughput systems

• Time slicing Resulting Best Practice


• Thread blocking (I/O, locks)
• Priority changes Do NOT create threads per request

Cost of Context Switching Preferred Solutions

• CPU cycles wasted • Thread pools (ExecutorService)


• Cache invalidation (L1/L2) • ForkJoinPool
• Pipeline flush • Async / reactive models

No business logic runs during context switch Interview One-Liner

“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)

Short Interview Answer


Senior Sound Bite
BLOCKED means a thread is waiting to acquire a monitor lock.
WAITING means a thread is waiting for another thread’s action or signal. “BLOCKED is about contention for a lock; WAITING is about coordination between threads.”

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

• Waiting for intrinsic lock


1. Thread Creation Is Expensive
• No timeout
• OS-level resources
• Automatically resumes when lock becomes available
• Stack allocation
Example • Scheduler involvement

synchronized(lock) { Thread pool


// if lock is held → BLOCKED
} • Creates threads once
• Reuses them for many tasks
WAITING State
2. Reduced Context Switching
When it happens
• Unbounded threads → excessive context switches
• Thread explicitly waits for a condition • Thread pools limit concurrency to CPU capacity

Caused by 3. Better Resource Management


• [Link]()
• Controls:
• [Link]()
o Max threads
• [Link]()
o Queue size
Key Characteristics • Prevents OutOfMemoryError

• Waiting for notification 4. Predictable Performance


• Needs explicit signal (notify, notifyAll, thread completion)
• Stable latency
Key Differences (Table) • Backpressure via task queues

Aspect BLOCKED WAITING


Waiting for Monitor lock Condition / signal
Caused by synchronized wait(), join()
Example Interview One-Liner

ExecutorService pool = [Link](10); “More threads than CPU cores usually reduces throughput due to context switching and resource
contention.”

Senior Insight Final Rapid-Fire Answers (Practice)


“Thread pools trade latency for stability — a system that slows down gracefully is better than one that
Q: Is BLOCKED better or WAITING?
crashes.”
A: Neither — they represent different synchronization needs.

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.

Short Interview Answer Q: Optimal number of threads?


A: Depends on CPU vs I/O bound workload.
Too many threads cause context-switching overhead, memory pressure, cache thrashing, and
can lead to system collapse instead of scalability.

1. CPU Context Switching Explosion


synchronized — Deep Dive
• CPU spends time switching instead of executing
• Throughput drops 1. What Is synchronized? (Definition)
2. Memory Exhaustion synchronized is a Java keyword that enforces mutual exclusion and establishes memory
visibility guarantees by using an object’s monitor lock.
• Each thread consumes stack memory
• Thousands of threads → OutOfMemoryError In simple words:

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 }

Interview-ready sentence: Implications:

“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

3. Monitor Lock (Intrinsic Lock) Important interview note:

Synchronization is object-specific, not method-specific.


A monitor is an internal lock associated with every Java object, used for synchronization.

Key points: 5. synchronized Static Method


• Automatically managed by JVM public static synchronized void log() {
• Used by: // static shared data
o synchronized }
o wait(), notify(), notifyAll()
• Only one thread can hold a monitor at a time What is locked?
Thread states: The Class object ([Link])

• Lock free → thread enters Why?


• Lock held → other threads BLOCKED
• Lock released → next thread acquires • Static data belongs to the class, not instances
• JVM has exactly one Class object per class
4. synchronized Instance Method
Equivalent code:
public synchronized void increment() {
synchronized([Link]) {
count++;
// method body
}
}

What is locked? Implications:


The current object (this)
• Blocks all threads across all instances
• Protects static shared state
Why?
Interview comparison:
• Instance methods operate on instance data
• Locking this protects that data Instance method → locks object
Static method → locks class
Equivalent code:

public void increment() {


synchronized(this) {
count++;
6. synchronized Block • JVM tracks lock ownership + reentry count
• Same thread → allowed
synchronized(lockObject) { • Different thread → blocked
// critical section
Why reentrancy matters:
}

• Enables clean object-oriented design


What is locked?
• Prevents self-deadlock
Exactly the object passed in parentheses
8. Happens-Before Guarantee
Why use synchronized blocks?
Releasing a synchronized lock happens-before acquiring the same lock by another thread.
• Fine-grained control
• Better performance This means:
• Avoid locking unnecessary code
• All writes inside synchronized block are:
o Flushed to main memory on exit
Example:
o Visible to the next thread entering
void update() {
doNonCriticalWork(); Memory effects:
synchronized(lock) {
• Lock release → write barrier
updateSharedData();
} • Lock acquire → read barrier
}
Interview gold line:
Best practice:
“synchronized provides a happens-before relationship, guaranteeing visibility and ordering.”
Always synchronize on a private final lock object, not this.
9. synchronized and Visibility
7. Reentrancy
synchronized guarantees visibility of shared variables.
Java’s synchronized is reentrant, meaning a thread can acquire the same lock multiple times
Why?
without deadlock.
• JVM flushes thread-local caches on lock release
Example: • Reloads values on lock acquire

synchronized void methodA() { So:


methodB();
} • No stale data
• No partial updates
synchronized void methodB() { • Safe publication
// same lock
} Comparison:

Why this works: • volatile → visibility only


• synchronized → visibility + atomicity + ordering
Interruptible
10. Does synchronized Prevent Reordering?
Simple & safe

Yes — within synchronized blocks.

• 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?

Only if they synchronize on the same object


11. Performance Characteristics
Modern JVMs have heavily optimized synchronized. ❓ Is synchronized reentrant?

Old myth: Yes

"synchronized is slow" ❓ Is synchronized thread-safe?

Reality: Yes, but not scalable under heavy contention

JVM uses: 14. When to Use synchronized


• Biased locking (no contention)
Use when:
• Lightweight locking
• Adaptive spinning • Simplicity matters
• Lock coarsening • Low to medium contention
• Lock elision • Complex logic inside critical section

When synchronized becomes expensive: Avoid when:

• High contention • High-performance systems


• Long-running critical sections • Fine-grained concurrency needed
• Many threads competing for same lock • Non-blocking algorithms required

Rule: 15. Final Interview Summary


Cost depends on contention, not keyword.
“The synchronized keyword enforces mutual exclusion and memory consistency by locking an
object’s monitor. Instance methods lock the current object, static methods lock the Class object,
12. synchronized vs Lock (High-Level) and synchronized blocks lock a specified object. Java synchronization is reentrant and establishes
happens-before guarantees, ensuring visibility and ordering. Modern JVMs optimize synchronization
Feature synchronized Lock heavily, but contention can still affect performance.”
Explicit unlock
Fairness
Locks & Atomic Classes 4. Fair vs Unfair Locks

Unfair Lock (Default)


SECTION A: Locks
Threads can “barge in” and acquire the lock even if others are waiting.
1. Why Explicit Locks Exist Pros:

Explicit locks ([Link]) exist to provide more flexible and powerful • Higher throughput
locking than synchronized. • Better CPU utilization

Limitations of synchronized: Cons:

• No fairness control • Starvation possible


• No timed lock acquisition
• Cannot interrupt while waiting Fair Lock
• One implicit lock per object only
new ReentrantLock(true);
Explicit locks solve these.
Lock is granted in FIFO order.

2. What Is ReentrantLock? Pros:

ReentrantLock is a mutual exclusion lock with the same basic behavior as synchronized, but • Predictable
with extended capabilities. • Prevents starvation

Key features: Cons:

• Reentrant (same thread can re-acquire) • Lower throughput


• Explicit lock/unlock • More context switching
• Optional fairness
• Advanced lock acquisition methods Interview insight:

Fairness trades performance for predictability.


3. Reentrancy (Lock Perspective)
5. tryLock()
A thread holding a lock can acquire it again without blocking.

Why needed: Attempts to acquire a lock without blocking.

• Avoids self-deadlock if ([Link]()) {


try {
• Supports layered method calls
// critical section
Internally: } finally {
[Link]();
• Lock tracks owner thread }
• Maintains hold count }

Variants:
• tryLock() 9. AtomicInteger
• tryLock(timeout, unit)

Use cases: Provides atomic operations on integers without locks.

Common methods:
• Avoid deadlock
• Timeout-based locking • get()
• Responsive systems • set()
• incrementAndGet()
6. Lock vs synchronized • compareAndSet()

Feature synchronized ReentrantLock Internally:


Explicit unlock
• Uses CAS + volatile
Fairness
tryLock
Interruptible 10. CAS (Compare-And-Swap)
Simplicity
CAS updates a value only if it matches an expected value.

Atomic operation:
7. When to Use Locks
if (value == expected)
Use when: value = newValue

• Fine-grained control required


• Need timeout or fairness Why CAS is powerful:
• Need multiple condition queues
• Non-blocking
Avoid when: • Avoids thread suspension
• High scalability
• Simple mutual exclusion needed

11. ABA Problem (Conceptual)


SECTION B: Atomic Classes
CAS cannot detect if a value changed and changed back.
8. What Are Atomic Classes?
Example:

Atomic classes provide lock-free, thread-safe operations on single variables using CAS. • Value: A → B → A

Examples: • CAS sees A and succeeds

• AtomicInteger Why dangerous:


• AtomicLong
• Intermediate state ignored
• AtomicReference
Solution:

• 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:

13. Atomic vs volatile “Which is better?”

Feature volatile Atomic Answer:


Atomicity
Visibility “It depends on the use case — simplicity, fairness, contention, and complexity of shared state.”
Lock-free
Use case Flags Counters

Executors & Thread Pools — Deep Dive


14. Atomic vs synchronized
Feature Atomic synchronized
1. Why ExecutorService Exists
Blocking
Scalability High Lower ExecutorService separates task submission from task execution.
Complex logic
Single variable Problems with manual thread management:

• Creating threads is expensive


• No reuse of threads
15. When NOT to Use Atomics
• Hard to manage lifecycle
• No control over concurrency
• Multiple variables must change together
• Easy to leak threads
• Invariants must be preserved
• Complex state transitions
What ExecutorService provides:
Use locks instead.
• Thread reuse (thread pools)
• Task queueing
• Lifecycle management Maximum Pool Size
• Graceful shutdown
• Better resource utilization Maximum number of threads allowed.

Interview line: • Used only when queue is full


• Beyond core size
“Executors abstract away thread management and provide controlled concurrency.”
Interview insight:
2. What Is a Thread Pool? Threads grow only after the queue fills up.

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.

Key parameters: 6. Thread Pool Types (Executors Factory Methods)


ThreadPoolExecutor(
corePoolSize,
6.1 Fixed Thread Pool
maximumPoolSize,
[Link](n)
keepAliveTime,
timeUnit,
workQueue,
threadFactory,
Internals:
rejectionHandler
• corePoolSize = maxPoolSize = n
)
• Unbounded queue (LinkedBlockingQueue)

Understanding this class = understanding executors. Behavior:

• Fixed number of threads


4. Core Pool Size vs Maximum Pool Size
• Tasks queue up when threads are busy

Core Pool Size


Use case:
Minimum number of threads kept alive (even if idle).
• CPU-bound tasks
• Created eagerly (or lazily when tasks arrive) • Stable workloads
• Never destroyed unless shutdown
Risk:
• Queue can grow unbounded → OutOfMemoryError Guarantees:

6.2 Cached Thread Pool • Tasks execute sequentially


• Order preserved
[Link]()
Use case:
Internals:
• Event processing
• corePoolSize = 0 • Serialized access to shared resource
• maxPoolSize = Integer.MAX_VALUE
• Queue = SynchronousQueue 6.4 Scheduled Thread Pool
Behavior: [Link](n)

• Creates new thread for each task if none idle


• Reuses idle threads Supports:
• Threads removed after 60s idle
• Delayed tasks
❗ Why newCachedThreadPool() Is Dangerous (INTERVIEW FAVORITE) • Periodic execution

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)

6.3 Single Thread Executor Unbounded Queue (LinkedBlockingQueue)

[Link]() • Tasks wait indefinitely


• No thread growth beyond core

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)

shutdown(); // graceful Examples:


shutdownNow(); // forceful
• REST calls
• File IO
Best practice: • DB queries

[Link](); Thread pool size:


[Link](timeout);
#threads ≈ cores × (1 + wait/compute ratio)

Why important:

• Prevent thread leaks


• Allow tasks to finish
11. How Do You Size a Thread Pool? (INTERVIEW GOLD)
• Release resources
General formula:
9. Rejection Policies Threads = CPU cores × (1 + wait time / compute time)

When task cannot be accepted:


Practical guidance:
• AbortPolicy (default) → exception
• CallerRunsPolicy → caller executes • CPU-bound → cores or cores + 1
• DiscardPolicy → silently drop • IO-bound → higher than cores
• DiscardOldestPolicy → drop oldest task • Measure & tune
Interview tip:
ForkJoinPool Internals
Mention monitoring and tuning, not guessing.

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.

Avoid factory methods in production. Key points:

Why? • Designed for many small tasks


• Optimized for CPU-bound workloads
• Hidden unbounded queues
• Uses work-stealing to maximize throughput
• Hidden thread limits

Preferred: 2. Fork/Join Concept


new ThreadPoolExecutor(
• Fork: Split a task into smaller subtasks
core,
• Join: Combine results from subtasks
max,
keepAlive,
Example:
unit,
new ArrayBlockingQueue<>(size) • Recursive sum of an array:
);
sum(array[0..1000]) = sum(array[0..500]) + sum(array[501..1000])

13. Common Interview Traps FJP manages threads executing these subtasks efficiently.

• Cached thread pool is scalable


3. Thread Model
• Fixed thread pool cannot cause OOM
• More threads = faster
• FJP uses worker threads, which are platform threads.
• Executors manage shutdown automatically • Default: [Link]().availableProcessors() threads
• Each thread has its own deque (double-ended queue) for tasks
14. One-Page Interview Summary
4. Task Scheduling
“ExecutorService provides a high-level abstraction for managing threads and executing tasks. Thread
pools reuse threads to improve performance and control concurrency. Different pool types serve
Worker Deques
different workloads, and improper sizing or unbounded resources can lead to serious performance
issues. CPU-bound and IO-bound tasks require different sizing strategies, and graceful shutdown is
• Each thread maintains LIFO deque of tasks
essential for resource management.”
• Newly forked tasks go to top of the deque
• Thread executes tasks from top (LIFO) → improves cache locality

Work Stealing

• When a thread’s deque is empty:


o It steals tasks from the bottom of other threads’ deques (FIFO) UncaughtExceptionHandler handler,
o Reduces contention, keeps threads busy boolean asyncMode // FIFO vs LIFO (true = FIFO for local tasks)
• Work stealing is lock-free (via CAS operations) )

Interview insight: • parallelism → usually #cores


• asyncMode → true for FIFO local tasks (stream pipelines)
LIFO for local tasks → better locality; FIFO for stolen tasks → reduces contention
10. ForkJoinPool vs Fixed Thread Pool
5. Task Execution Flow
Feature ForkJoinPool FixedThreadPool
1. Submit a ForkJoinTask Thread strategy Work-stealing Queue tasks to threads
2. Task added to submitter’s deque or external queue Task type Small, recursive Independent tasks
3. Worker thread executes tasks: Blocking Supports via managedBlocker Blocks thread
a. fork() → push on deque Performance High CPU utilization Lower if tasks small
b. join() → pop from deque, or help complete tasks
4. Idle threads steal tasks from other deques 11. Common Interview Questions

6. ForkJoinTask Types ❓ Why LIFO for local tasks?

• RecursiveAction → no result • Improves cache locality


• RecursiveTask<V> → returns result • Reduces overhead for recursively forked subtasks
• CountedCompleter → more complex async task composition
❓ Why FIFO for stolen tasks?
7. Stealing Details • Fairness across threads
• Reduces conflicts
• Stealing happens only from other threads’ deques
• LIFO → fast for local subtasks
❓ How does work stealing scale?
• FIFO → fair for stealing threads
• Low contention → high scalability
• Idle threads steal work from others → keeps all cores busy
• No global queue bottleneck → scales to many cores
8. Blocking Support
❓ What about blocking tasks?
• FJP can handle tasks that block (e.g., I/O) via managedBlocker
• Use managedBlocker
[Link](() -> { /* blocking call */ });
• Or use virtual threads in modern Java (better for I/O)
• Helps FJP create temporary threads if blocking occurs
• Prevents underutilization of CPU threads 12. ForkJoinPool & Parallel Streams

9. Common ForkJoinPool Constructor Parameters • [Link]() uses commonPool:

[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.”

14. Pitfalls 2. What Is CompletableFuture?

• 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());

CompletableFuture — Deep Dive • Uses [Link]() by default


• Executor can be customized
1. Why CompletableFuture Exists
4. Async Pipelines (CORE CONCEPT)
CompletableFuture exists to enable non-blocking, asynchronous programming with
composable pipelines. Async pipelines allow chaining dependent asynchronous steps without blocking.

Problems with older approaches: Example:

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

[Link](ex -> fallback());


thenApply

Used when the function returns a value.


handle
CompletableFuture<User> cf =
[Link]((result, ex) -> {
fetchUserAsync()
if (ex != null) return fallback();
.thenApply(user -> enrich(user));
return result;
Result: });

CompletableFuture<User>
whenComplete

thenCompose [Link]((res, ex) -> log());

Used when the function returns another CompletableFuture.


Difference:
CompletableFuture<User> cf =
fetchUserAsync() • exceptionally → recover
.thenCompose(user -> fetchDetailsAsync(user)); • handle → transform
• whenComplete → side effects only
Without thenCompose, you’d get:

CompletableFuture<CompletableFuture<User>> 8. Combining Multiple Futures


Useful for parallel execution.
Interview one-liner:
thenCombine
“Use thenApply for mapping, thenCompose for flattening.”
[Link](cf2, (a, b) -> a + b);
6. Async vs Sync Variants
• thenApply() → may run on same thread allOf
• thenApplyAsync() → runs asynchronously
[Link](f1, f2, f3);
Same applies to:

• 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

14. One-Page Interview Summary


Senior signal line:

“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

Two threads → lost updates.


3. Livelock
Symptoms
Definition
• Inconsistent results
• Hard-to-reproduce bugs Threads are not blocked but keep reacting to each other, making no progress.
• Test passes, prod fails
Example
Prevention
• Two threads keep releasing locks to “be polite”
• synchronized • Both retry endlessly
• Locks
• Atomic classes Difference from deadlock
• Immutability
Deadlock Livelock
Threads Threads
2. Deadlock blocked running
No progress No progress
Definition Easy to detect Harder

Deadlock occurs when two or more threads wait forever for each other’s locks. Prevention

Necessary conditions (Coffman conditions) • Random backoff


• Retry limits
1. Mutual exclusion
• Simplify logic
2. Hold and wait
3. No preemption
4. Circular wait
4. Starvation

Classic example Definition

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

• Lock ordering • Writer starves while readers dominate


• Timeouts (tryLock)
Prevention kill -3 <pid>

• Fair locks
Look for:
• Priority tuning
• Reduce lock hold time • "Found one Java-level deadlock"
• BLOCKED threads
5. False Sharing (Conceptual) • Locked monitors

Definition Step 2: Analyze Lock Ownership

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

Mitigation Step 4: Fix Strategy

• Padding • Enforce lock ordering


• @Contended (JVM flag required) • Use tryLock with timeout
• Reduce shared mutable fields • Reduce scope of synchronized blocks
• Replace with higher-level concurrency utilities
Interview note:
Senior answer line:
False sharing is about cache, not Java memory safety.
“I use thread dumps to identify circular lock dependencies and then fix lock ordering or replace
blocking locks with time-bounded acquisition.”

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.

Why this question exists:


• Deadlock requires two threads only
Many developers confuse Spring’s lifecycle management with thread safety.
• Livelock is same as deadlock
• Starvation cannot happen in Java Spring:
• False sharing causes incorrect results
• Manages bean creation
• Manages scopes
9. Quick Comparison Table • Does NOT manage synchronization of your business logic

Problem Progress CPU Root Cause Senior one-liner:


Race condition Yes (wrong) Normal No sync
Deadlock No Low Circular wait “Spring does not make your code thread-safe. Singleton just means one instance, not safe instance.”
Livelock No High Overreaction
Starvation Partial Normal Unfairness
False sharing Yes (slow) High Cache contention
2. Why Spring Beans Are Singleton by Default

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

A stateless bean holds no mutable, request-specific data in instance fields.

Thread Safety in Spring — Interview Notes Example (SAFE):

@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

Why safe: • Caching request data in fields


• Reusing mutable objects (Date, List, Map)
• No shared mutable state • Using instance variables as temporary storage
• All data is method-local (thread stack) • Using non-thread-safe collections
• Each thread has its own stack

Interview line:
6. Request Scope vs Singleton Scope

“Stateless beans are naturally thread-safe because they don’t share mutable state.” Singleton (default)

• One instance per container


4. Why Mutable Fields in Singleton Beans Are Dangerous
• Shared across all requests
• Must be stateless or thread-safe
Example (UNSAFE):
@Scope("singleton")
@Service
class OrderService {
private int total;
Request Scope
public void add(int value) { One bean instance per HTTP request
total += value;
} @Scope(value = WebApplicationContext.SCOPE_REQUEST)
}

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

• Method is small Trade-offs:


• Logic looks “safe”
• Higher memory usage
It’s still unsafe unless synchronized or atomic. • Slower creation
• Not usable outside web context
Key rule:

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):

1. Stateless design 14. Final Senior Signal Line


2. Immutability
3. Thread-local storage (carefully) If asked:
4. Synchronization / locks (last resort)
“Is Spring thread-safe by default?”
5. Concurrent data structures
Answer:
Senior advice:
“No. Spring is concurrent by design, but thread safety depends entirely on how you write your beans.”
“Design for statelessness first; synchronization is a fallback.”

10. Why Controllers Are Also Singleton


Spring MVC Controllers:
ThreadLocal in Spring — When and When NOT
• Are singleton by default
1. What Is ThreadLocal?
• Handle multiple requests concurrently

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:

• Reads its own value • Transactions are thread-bound


• Cannot see other threads’ values • You cannot share transactions across threads

2. Why ThreadLocal Is Used in Spring ✅ 3. Cross-Cutting Concerns

Spring heavily relies on ThreadLocal internally for: • Logging (MDC)


• Tracing
• Transaction context • Auditing
• Security context
• Request context Example:
• Session attributes
[Link]("traceId", id);
Examples:

• TransactionSynchronizationManager 4. When NOT to Use ThreadLocal (VERY IMPORTANT)


• SecurityContextHolder
• RequestContextHolder
❌ 1. In Thread Pools (WITHOUT CLEANUP)
Key idea:
Thread pools reuse threads.
Spring binds contextual data to the current thread.
Danger:

3. When ThreadLocal Is Appropriate (GOOD USE CASES) • Data leakage between requests
• Security vulnerabilities
✅ 1. Request / Context Data • Memory leaks

• User ID Interview warning:


• Correlation ID
“ThreadLocal + thread pool without cleanup is a production bug.”
• Locale
• Security principal
❌ 2. As a Replacement for Method Parameters
Example:
Bad design:
[Link]().getAuthentication();
ThreadLocal<User> user;

✅ 2. Transaction Context Instead:

Spring binds: • Pass explicitly


• Use immutability
• Transaction
• Connection ❌ 3. Long-Lived Data
• EntityManager
ThreadLocal values live:
to the current thread.


As long as thread lives
Not GC-friendly
Transaction Boundaries & Thread Safety
❌ 4. Async / @Async / CompletableFuture 7. How Spring Transactions Work
ThreadLocal does NOT propagate automatically. Spring transactions are bound to the current thread using ThreadLocal.

@Async When you call:


public void asyncMethod() {
// ThreadLocal is EMPTY here @Transactional
} public void process() { }

Needs explicit propagation. Spring:

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:

try { A transaction is thread-bound and must not be shared across threads.


[Link](value);
} finally { Why:
[Link]();
} • JDBC Connection is not thread-safe
• EntityManager is not thread-safe
• Isolation guarantees break
Senior line:

“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.”

10. @Async + @Transactional Trap


@Async
@Transactional
🧪 Task 1: Race Condition (Mandatory Hands-On)
public void asyncTx() { }
Goal
Important:
• See incorrect results due to race condition
• Transaction starts in async thread • Fix using synchronized
• Not inherited from caller • Fix using AtomicInteger

Common misunderstanding:

“Transaction flows to async method”

11. Transaction Boundaries and Shared State


Even inside a transaction:

• Multiple threads modifying shared state = unsafe


• Transactions do NOT provide thread safety

Key clarification:

Transactions ensure data consistency at DB level, not thread safety in memory.

12. Correct Mental Model


Concept Provides
Transaction DB consistency
ThreadLocal Thread confinement
Synchronization Thread safety
Scope Lifecycle
1️⃣ Race Condition Example (BROKEN CODE) Why This Happens

counter++ is:
Code
1. Read
2. Increment
3. Write

Multiple threads interleave → lost updates

Interview line:

“Race conditions happen when compound operations run without synchronization.”

Expected Result

Counter = 200000

Actual Result (varies)

Counter = 137421
Counter = 182344

2️⃣ Fix Using synchronized Atomicity + visibility
• Slower under contention

Code Interview insight:

“synchronized guarantees correctness but may reduce scalability.”

Result

Counter = 200000

What Changed

• Mutual exclusion
3️⃣ Fix Using AtomicInteger (Preferred)
✅ Task 1 Summary
Code
Version Correct Blocking Scales
Broken
synchronized
AtomicInteger

🧪 Task 2: Thread Pool Experiment


Goal
• See how pool size affects execution
• Understand CPU-bound vs IO-bound tasks

Why This Works

• Uses CAS
• Lock-free
• Better under contention

Interview comparison:

“AtomicInteger scales better than synchronized for simple counters.”


1️⃣ Test Program

Code

Key Observations

• Only 2 tasks at a time


• Remaining tasks wait in queue
• Threads are reused

3️⃣ Change Pool Size


Try:

[Link](5);

Observation:

• 5 tasks run in parallel


• Faster completion
• More threads = more memory/context switching

4️⃣ Cached Thread Pool (DANGEROUS)


ExecutorService executor = [Link]();

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

Virtual Threads (Project Loom)


1. What Are Virtual Threads?
Virtual threads are lightweight threads managed by the JVM, not the OS, designed to handle
IO-Bound Task massive concurrency with minimal resource cost.

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.”

2. Why Virtual Threads Exist

Problem with Platform Threads (Traditional Threads):

• 1 Java thread ≈ 1 OS thread


• OS threads are expensive
• Blocking wastes threads
Interview explanation:
• Thread pools limit concurrency
“CPU-bound tasks need limited threads, IO-bound tasks benefit from more threads.”
Example problem:

• 10,000 concurrent requests


• Each request blocks on I/O
✅ Task 2 Summary • OS threads get exhausted

Pool Type Behavior What Virtual Threads Fix:


Fixed Predictable
Cached Risky • Cheap to create (millions possible)
Too small Underutilization
• Blocking does not waste OS threads
Too large Context switching
• No callback hell
• No CompletableFuture everywhere Using Executor (Recommended):

Key motivation: ExecutorService executor =


[Link]();
Make blocking cheap again.
[Link](() -> {
3. Platform Threads vs Virtual Threads [Link](1000);
});
Aspect Platform Thread Virtual Thread
Managed by OS JVM
Creation cost High Very low Each task → new virtual thread
Blocking Blocks OS thread JVM parks thread No pooling required.
Count Thousands max Millions
Programming model Synchronous Synchronous Interview tip:

“With virtual threads, one-thread-per-request becomes viable again.”

4. How Virtual Threads Work (Conceptual)


6. Virtual Threads + Blocking I/O
Virtual threads are scheduled by the JVM onto a small pool of carrier (platform) threads.
This is the killer feature.
Key concepts: Code like this is now scalable:

• Virtual Thread → lightweight void handleRequest() throws Exception {


• Carrier Thread → real OS thread String data = [Link](); // BLOCKING
• Mount / Unmount → JVM pauses & resumes [Link](data); // BLOCKING
}
When a virtual thread blocks (I/O):
Previously:
1. JVM unmounts it from carrier thread
2. Carrier thread runs another virtual thread • Needed async frameworks
3. Blocked VT resumes later • Or huge thread pools

Important: Now:

Blocking calls like sleep, socket, JDBC are virtual-thread-friendly. • JVM handles blocking efficiently

5. Creating Virtual Threads 7. Virtual Threads vs CompletableFuture

Simple example: Virtual Threads CompletableFuture


Synchronous code Async pipelines
[Link](() -> { Easy to read Harder to reason
[Link]("Hello from virtual thread"); Blocking-friendly Non-blocking
}); Great for services Great for composition

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.”

❌ Long synchronized blocks 12. Virtual Threads & Spring


• Can pin carrier threads
Spring Boot 3.2+ supports virtual threads.

❌ ThreadLocal misuse Example:

• ThreadLocal still exists [Link]=true


• Millions of VTs → memory risk
Works well with:
Interview warning:
• Spring MVC
“Virtual threads solve blocking, not bad synchronization.” • @Transactional
• JDBC

10. Pinning Problem (Advanced) Transactions still:

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

14. Common Interview Trap Questions

❓ “Do virtual threads remove the need for thread pools?”

✔ Mostly yes — for IO-bound tasks.

❓ “Are virtual threads faster than platform threads?”

✔ Creation is faster, execution speed is same.

❓ “Do virtual threads make code thread-safe?”

No. Same concurrency rules apply.

❓ “Do transactions work with virtual threads?”

✔ Yes. Transactions are thread-bound, and VTs are real threads.

15. One-Page Interview Summary


“Virtual threads are lightweight JVM-managed threads that enable scalable blocking I/O. They
decouple concurrency from OS threads, allowing millions of concurrent tasks with simple
synchronous code. While they simplify concurrency, they do not eliminate synchronization issues or
replace proper design.”

16. Senior Closing Line (Use This)


“Virtual threads let me write simple blocking code without sacrificing scalability, but I still apply the
same concurrency discipline as with platform threads.”

You might also like