๐งต 1. Core Concepts of Multithreading
| Concept | Explanation |
|---|---|
| Thread | Smallest unit of execution; runs independently. |
| Runnable / Callable | Represent units of work (Callable returns a result). |
| Thread Safety | Multiple threads safely accessing shared data (e.g., using synchronized, locks, atomics). |
| Race Condition | Occurs when threads access shared data without proper synchronization. |
| Deadlock | Two or more threads waiting on each other to release locks. |
⚙️ 2. Executors Framework
Introduced in Java 5 to manage thread creation and pooling efficiently.
Executors (Thread Pools)
๐น Why Use Executors?
-
Creating threads manually is costly and inefficient.
-
Executors manage a pool of threads.
-
Avoids thread exhaustion & allows task reuse.
๐น Types of Executors
| Type | Best Use Case |
|---|---|
newFixedThreadPool(n) | Predictable concurrency, limited threads |
newCachedThreadPool() | Burst loads, short-lived tasks |
newSingleThreadExecutor() | Sequential task execution |
newScheduledThreadPool(n) | Scheduled/periodic tasks |
✅ Interview Answer: “We used a fixed thread pool for CPU-bound tasks to avoid excessive context switching.”
๐ง 3. ForkJoinPool (Java 7+)
Used for parallelism—divides tasks into smaller subtasks (divide-and-conquer).
๐ฆ Core Classes:
-
ForkJoinPool: Specialized thread pool to execute fork/join tasks. -
RecursiveTask<V>: Used when tasks return a result. -
RecursiveAction: Used when tasks do not return a result.
Used for parallelism — especially in recursive tasks like:
-
Sorting
-
File searches
-
Data crunching
๐น Based on work stealing algorithm:
-
Idle threads steal tasks from busy ones
| Feature | Benefit |
|---|---|
| Work-stealing | Idle threads can "steal" tasks from others—boosts efficiency. |
| RecursiveTask / RecursiveAction | Handles subtasks with/without results. |
✅ Interview Answer: “We parallelized complex data transformations with ForkJoinPool, which significantly reduced latency due to work-stealing.”
๐ฎ 4. CompletableFuture (Java 8+)
Asynchronous programming without blocking threads.
Introduced in Java 8 for async programming.
-
Chain operations with callbacks (
thenApply,thenAccept) -
Combine multiple futures
-
Handle exceptions (
exceptionally,handle)
๐ Use Cases
-
Async I/O and microservice orchestration
-
Parallel API calls
-
Composing dependent tasks without blocking
|
|---|
✅ Interview Answer: “We used
CompletableFutureto orchestrate three async microservice calls and combine their results withthenCombine, reducing response time from 2s to under 800ms.”
⚠️ 5. Best Practices
| Practice | Benefit |
|---|---|
Use Executors, not raw Thread | Scalable, better resource control |
| Use Thread-safe collections | Avoid ConcurrentModificationException |
| Prefer immutable objects | Reduce need for synchronization |
| Monitor thread pools | Detect bottlenecks (ThreadPoolExecutor#getActiveCount) |
| Avoid blocking in async flows | Use thenCompose instead of join() |
✅ Pro Interview Template
"I chose
CompletableFuturebecause we needed non-blocking orchestration of three APIs. It fits well into our microservices architecture and allows us to write readable, async code usingthenApplyandthenCombine, instead of managing threads manually. This keeps our service responsive and scalable."
Thread Management (ExecutorService)
By default, CompletableFuture uses the ForkJoinPool.commonPool(), but you can provide a custom thread pool:
Best Practices
| Practice | Why it Matters |
|---|---|
Use ExecutorService | Manages thread pooling safely |
Always shutdown() executors | Prevent resource leaks |
Prefer Callable over Runnable | Allows exception handling and result return |
Use CompletableFuture for async chains | Cleaner and non-blocking code |
Use ForkJoinPool for recursive divide-and-conquer | Better performance for large tasks |
⚠️ Common Pitfalls
| Pitfall | Explanation |
|---|---|
Blocking on .get() in main thread | Kills async benefit; prefer then* chains |
| Forgetting to shutdown executors | Causes app to hang |
| Using too many threads | Thread contention, memory pressure |
| Not handling exceptions in async tasks | Can silently fail or break chains |
Difference between synchronized and ReentrantLock
✅ 1. synchronized (Intrinsic Lock)
๐น What is it?
A language-level keyword that provides mutual exclusion using the intrinsic lock (monitor) of an object.
๐ง Syntax:
✅ Features:
-
Simple to use
-
Automatically releases the lock (even on exceptions)
-
Synchronized blocks/methods are reentrant by default
❌ Limitations:
-
No try-lock capability (you must wait)
-
No timeout or interrupt handling
-
Less flexible (can’t check lock status or manage fairness)
✅ 2. ReentrantLock (Explicit Lock)
๐น What is it?
A class from java.util.concurrent.locks package that gives fine-grained control over locking.
๐ง Syntax:
✅ Features:
-
Try-locking:
tryLock()to attempt without blocking -
Timeouts:
tryLock(timeout, unit) -
Interruptible:
lockInterruptibly() -
Fairness: Option to grant locks in FIFO order
-
Condition variables: Like
wait()/notify()but more powerful
๐ฏ When to Use What?
| Use Case | Recommendation |
|---|---|
| Simple mutual exclusion | synchronized |
| Need to try-lock or avoid blocking | ReentrantLock |
| Require fairness (FIFO lock order) | ReentrantLock |
| Need multiple condition variables | ReentrantLock |
| Short critical section, low contention | synchronized |
difference between Runnable and Callable?
✅ Runnable vs Callable in Java
| Feature | Runnable | Callable<V> |
|---|---|---|
| Package | java.lang | java.util.concurrent |
| Return value | No return value (void) | Returns a result (V) |
| Can throw exception | Cannot throw checked exceptions | Can throw checked exceptions |
| Method to override | run() | call() |
| Submit via | Thread or ExecutorService | Only ExecutorService |
| Used with Future? | No (unless wrapped) | Yes, returns Future<V> |
๐ง Code Example: Runnable
-
Does not return a result
-
Cannot throw checked exceptions
-
Executed via
ThreadorExecutorService
๐ง Code Example: Callable
-
Returns a result
-
Can throw checked exceptions
-
Executed using
ExecutorServiceonly
๐ง Interview Tip
“Use
Runnablewhen no result or exception is needed. UseCallablewhen the task needs to return a value or might throw a checked exception. In production-grade concurrent apps,CallablewithExecutorServiceandFutureprovides better control.”
✅ Stream vs ParallelStream in Java
| Feature | stream() | parallelStream() |
|---|---|---|
| Execution | Sequential (one thread) | Parallel (multiple threads from ForkJoinPool) |
| Performance | Better for small/IO-bound tasks | Better for large/CPU-bound tasks |
| Threading | Single-threaded | Multi-threaded (ForkJoinPool.commonPool) |
| Ordering | Preserves order by default | May not preserve order |
| Suitability | Simpler, more predictable | Requires thread-safety and careful design |
๐ Example: stream()
-
Runs sequentially from start to finish.
-
Easier to debug and reason about.
⚡ Example: parallelStream()
-
Splits data and runs parts concurrently using multiple threads.
-
Faster for large data sets — but only if thread-safe and no shared state.
No comments:
Post a Comment