Explore common patterns, pitfalls, and best practices in Java concurrency with a focus on ExecutorService and CompletableFuture. Perfect for brushing up on key technical concepts before an interview.
Which core problem does ExecutorService solve for Java developers?
Explanation: ExecutorService abstracts thread management by using a thread pool, letting developers submit tasks without handling threads directly. Database management and garbage collection are unrelated. Random number generation is not its core purpose.
Why is creating threads manually in Java generally discouraged?
Explanation: Creating threads manually can exhaust system resources and offers no pooling or management. Conciseness and performance are not the main reasons, and manual threads are not pooled automatically.
What is a key difference between the execute() and submit() methods in ExecutorService?
Explanation: execute() is fire-and-forget, while submit() returns a Future for getting results or handling exceptions. Both run on worker threads, not the main thread. There is a clear difference between them.
If an ExecutorService thread pool is full, what determines how new tasks are handled?
Explanation: A RejectionPolicy decides what happens when the thread pool is full, such as aborting or discarding tasks. Task priority, OS, and garbage collector do not directly affect this behavior.
According to best practice, how should you size a thread pool for CPU-bound tasks?
Explanation: For CPU-bound tasks, the recommended size is the number of CPU cores plus one. Using double, half, or a fixed value like 10 may not be optimal for performance or resource management.
Why was CompletableFuture introduced in Java?
Explanation: CompletableFuture was designed to support non-blocking workflows and easy composition of asynchronous steps. Thread groups, synchronized replacement, and garbage collection are unrelated features.
How is CompletableFuture better than Future?
Explanation: CompletableFuture enables chaining and advanced error handling not available in Future. The number of imports, task type restrictions, and ExecutorService dependencies are not the main differences.
When working with CompletableFuture, when should you use thenCompose() instead of thenApply()?
Explanation: thenCompose() is used to flatten nested CompletableFutures resulting from dependent async calls. thenApply() is for simple result transformation, not for handling sequential or exception logic.
Which CompletableFuture method is commonly used to run several tasks in parallel and wait for all to complete?
Explanation: CompletableFuture.allOf() enables waiting for multiple parallel tasks to finish. get() is for retrieving results, runAsync() for launching single tasks, and cancel() for interrupting tasks.
How can exceptions be handled in a CompletableFuture pipeline?
Explanation: CompletableFuture provides .exceptionally() and .handle() for exception handling in async chains. Catching in run(), synchronized, or thread priority do not address asynchronous exception management.