![]() |
VOOZH | about |
This focuses on advanced interview questions around Java's concurrency utilities and modern parallelism techniques. It covers the Executor framework, thread pools, Callable, Future, and CompletableFuture for asynchronous programming. You’ll also explore synchronization tools like ReentrantLock, CountDownLatch, Semaphore, Phaser, and atomic classes, essential for writing scalable, non-blocking concurrent applications.
In Java, the Executor framework helps run tasks in a managed thread pool without manually creating threads. Two common ways to run tasks are execute() and submit(), which differ in result handling and exception management.
Use Case Example:
An ExecutorService is a high-level API in Java that manages a pool of threads to run tasks efficiently. Knowing its lifecycle and shutdown methods is important to avoid leaving threads running or losing tasks.
Pitfall: Calling shutdownNow() may interrupt important computation and lead to inconsistency if not handled.
In Java, threads can perform tasks using Runnable or Callable. While both allow running code in parallel, Callable is more powerful because it can return results and throw checked exceptions, making it ideal for tasks where you need a response or error handling.
Example:
A ThreadPoolExecutor allows you to manage a pool of threads efficiently, controlling how tasks are queued and executed. Proper configuration is essential to balance performance, avoid resource wastage, and prevent thread starvation or memory issues.
A ThreadPoolExecutor is configured using parameters like:
Example:
ScheduledExecutorService allows you to schedule tasks to run after a delay or repeatedly at fixed intervals, making it ideal for periodic or delayed tasks.
Example:
Use case: FixedRate for heartbeats, FixedDelay for log processing
The Fork/Join framework is used to split large tasks into smaller subtasks that run in parallel and then combine the results. RecursiveTask allows subtasks to return results which are merged to get the final output.
class SumTask extends RecursiveTask<Integer> { /* ... */ }
int sum = new ForkJoinPool().invoke(new SumTask(arr, 0, arr.length));
In Java, both synchronized and ReentrantLock are used to achieve mutual exclusion, but ReentrantLock provides more advanced features for flexible concurrency control.
| Feature | synchronized | ReentrantLock |
|---|---|---|
| Interruptible | No | Yes |
| Try lock | No | Yes (tryLock()) |
| Fairness control | No | Yes |
| Condition object | No | Yes |
When to use ReentrantLock:
In Java, tryLock() is an advanced feature of ReentrantLock that tries to acquire a lock without blocking the thread. If the lock is unavailable, the thread can retry later or take an alternative action, rather than waiting indefinitely.
Example:
How it prevents deadlocks:
In Java, both ReadWriteLock and StampedLock allow multiple threads to read shared data concurrently while ensuring exclusive access for writers.
| Feature | ReadWriteLock | StampedLock |
|---|---|---|
| Read lock type | Blocking | Optimistic |
| Upgrading lock | No | Yes (carefully) |
Example:
Optimistic reads reduce contention, especially in read-heavy systems.
In Java, CountDownLatch is a synchronization tool that allows threads to wait until a set of operations in other threads completes. It has a fixed count and cannot be reused once the count reaches zero.
Example:
In Java, a Semaphore is a concurrency tool that controls access to a limited number of resources by allowing only a fixed number of threads to acquire the permit at a time.
Example:
Use Case: DB pool, printer access, API rate limiting
In Java, atomic classes (like AtomicInteger, AtomicLong) provide thread-safe, lock-free operations on single variables using Compare-And-Swap (CAS). They ensure updates are atomic without blocking threads, unlike traditional locks.
Example:
In multithreaded programming, AtomicInteger is often used to safely increment counters without explicit synchronization. However, even atomic operations can cause problems when combined with other non-atomic operations.
Example:
2 4 6 8 10
Explanation:
Wrap the combined operation in a synchronized block:
CompletableFuture is a Java class (from java.util.concurrent) that represents a future result of an asynchronous computation. You can start tasks in the background, chain multiple tasks together, and handle exceptions without blocking the main thread.
Example: Task Chaining
Output: Final Result: 13
Example: Exception Handling:
In Java, CompletableFuture allows you to run multiple asynchronous tasks. Sometimes you need to wait for all tasks to finish or proceed as soon as any task completes. Java provides allOf() and anyOf() methods for this purpose.
1. Wait for All Tasks: CompletableFuture.allOf()
Task 1, Task 2, Task 3
2. Wait for Any Task: CompletableFuture.anyOf()
First completed: Fast Task
anyOf() returns a CompletableFuture<Object> that completes as soon as any one of the futures completes.