Java Concurrency: ExecutorService and CompletableFuture Essentials Quiz

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.

  1. ExecutorService and Thread Management

    Which core problem does ExecutorService solve for Java developers?

    1. Generates random numbers in parallel
    2. Performs memory garbage collection
    3. Manages database transactions automatically
    4. Decouples task submission from thread execution

    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.

  2. Drawbacks of Manual Thread Creation

    Why is creating threads manually in Java generally discouraged?

    1. Manual thread creation can lead to resource exhaustion and lacks lifecycle control
    2. Threads created manually are automatically pooled
    3. It always performs better due to fewer layers
    4. It is more concise than using ExecutorService

    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.

  3. execute() vs submit() Methods

    What is a key difference between the execute() and submit() methods in ExecutorService?

    1. execute() does not return a result, submit() returns a Future
    2. execute() supports exception handling, submit() does not
    3. There is no difference between them
    4. submit() runs tasks on the main thread, execute() on a worker thread

    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.

  4. Thread Pool Saturation Handling

    If an ExecutorService thread pool is full, what determines how new tasks are handled?

    1. JVM garbage collector
    2. The configured RejectionPolicy
    3. Task priority settings
    4. Operating system version

    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.

  5. Thread Pool Sizing Best Practices

    According to best practice, how should you size a thread pool for CPU-bound tasks?

    1. Number of CPU cores plus one
    2. A fixed value of 10
    3. Twice the number of CPU cores
    4. Half the number of CPU cores

    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.

  6. CompletableFuture Introduction Purpose

    Why was CompletableFuture introduced in Java?

    1. To improve garbage collection
    2. To automatically create thread groups
    3. To replace the synchronized keyword
    4. To enable non-blocking, asynchronous, and composable programming

    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.

  7. CompletableFuture Advantages over Future

    How is CompletableFuture better than Future?

    1. It requires fewer import statements
    2. It can only be used for IO-bound tasks
    3. It offers chaining of async steps and better error handling
    4. It eliminates the need for ExecutorService

    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.

  8. thenApply() vs thenCompose() Usage

    When working with CompletableFuture, when should you use thenCompose() instead of thenApply()?

    1. When you need results immediately
    2. When handling checked exceptions
    3. When the next step returns a CompletableFuture
    4. When you want to run tasks sequentially

    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.

  9. Parallel Execution with CompletableFuture

    Which CompletableFuture method is commonly used to run several tasks in parallel and wait for all to complete?

    1. CompletableFuture.cancel()
    2. CompletableFuture.allOf()
    3. CompletableFuture.get()
    4. CompletableFuture.runAsync()

    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.

  10. Exception Handling in CompletableFuture

    How can exceptions be handled in a CompletableFuture pipeline?

    1. .exceptionally() or .handle() methods
    2. By setting the thread priority
    3. Using synchronized blocks
    4. By catching exceptions inside run() method only

    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.