Tuesday, July 22, 2025

• Multithreading, Concurrency (Executors, ForkJoin, CompletableFuture)

 

๐Ÿงต 1. Core Concepts of Multithreading

ConceptExplanation
ThreadSmallest unit of execution; runs independently.
Runnable / CallableRepresent units of work (Callable returns a result).
Thread SafetyMultiple threads safely accessing shared data (e.g., using synchronized, locks, atomics).
Race ConditionOccurs when threads access shared data without proper synchronization.
DeadlockTwo 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

ExecutorService executor = Executors.newFixedThreadPool(4); // 4 worker threads executor.submit(() -> { System.out.println("Running in: " + Thread.currentThread().getName()); }); executor.shutdown();

ExecutorService Variants

ExecutorDescription
newFixedThreadPool(n)Fixed number of threads
newCachedThreadPool()Creates new threads as needed
newSingleThreadExecutor()Single-threaded executor
newScheduledThreadPool(n)For scheduled/periodic tasks

๐Ÿง  Use Future<T> for getting return values from tasks.

 

TypeBest 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).

 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

ForkJoinPool pool = new ForkJoinPool(); pool.invoke(new RecursiveTask<Integer>() { /*...*/ });
FeatureBenefit
Work-stealingIdle threads can "steal" tasks from others—boosts efficiency.
RecursiveTask / RecursiveActionHandles 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)

CompletableFuture.supplyAsync(() -> fetchData()) .thenApply(data -> process(data)) .thenAccept(result -> save(result));


๐Ÿ”น Exception Handling

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Oops"); }).exceptionally(ex -> "Default Value"); System.out.println(cf.get()); // Default Value

๐Ÿ” Use Cases

  • Async I/O and microservice orchestration

  • Parallel API calls

  • Composing dependent tasks without blocking

MethodPurpose
supplyAsync()Start async task with result
thenApply()Transform result
thenCombine()Combine results of two futures
exceptionally()Handle errors gracefully

Interview Answer: “We used CompletableFuture to orchestrate three async microservice calls and combine their results with thenCombine, reducing response time from 2s to under 800ms.”


⚠️ 5. Best Practices

PracticeBenefit
Use Executors, not raw ThreadScalable, better resource control
Use Thread-safe collectionsAvoid ConcurrentModificationException
Prefer immutable objectsReduce need for synchronization
Monitor thread poolsDetect bottlenecks (ThreadPoolExecutor#getActiveCount)
Avoid blocking in async flowsUse thenCompose instead of join()

✅ Pro Interview Template

"I chose CompletableFuture because we needed non-blocking orchestration of three APIs. It fits well into our microservices architecture and allows us to write readable, async code using thenApply and thenCombine, instead of managing threads manually. This keeps our service responsive and scalable."



Best Practices

PracticeWhy it Matters
Use ExecutorServiceManages thread pooling safely
Always shutdown() executorsPrevent resource leaks
Prefer Callable over RunnableAllows exception handling and result return
Use CompletableFuture for async chainsCleaner and non-blocking code
Use ForkJoinPool for recursive divide-and-conquerBetter performance for large tasks

⚠️  Common Pitfalls

PitfallExplanation
Blocking on .get() in main threadKills async benefit; prefer then* chains
Forgetting to shutdown executorsCauses app to hang
Using too many threadsThread contention, memory pressure
Not handling exceptions in async tasksCan silently fail or break chains

 

No comments:

Post a Comment