Discussion points ExecutorService
This document contains classroom discussion questions about Java’s Callable, Future and ExecutorService. A complete answer key is provided at the bottom.
Consider:
Runnable task = () -> {
int result = 20 + 22;
};
1.1. What does Runnable.run() return?
1.2. How can the calling code obtain the calculated result?
1.3. Can Runnable.run() declare checked exceptions?
1.4. What problems might occur if a Runnable stores its result in a shared variable?
A simplified definition of Callable is:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
2.1. Why is Callable a functional interface?
2.2. What does the generic type V represent?
2.3. How does call() differ from Runnable.run()?
2.4. Why is the ability to declare checked exceptions useful?
Callable<Integer> task = () -> {
return 20 + 22;
};
3.1. What input does this lambda receive?
3.2. What type of value does it return?
3.3. Has the calculation happened when the Callable is created?
3.4. Can the lambda be shortened?
| Interface | Input | Output | Method |
|---|---|---|---|
Runnable | None | None | run() |
Callable<V> | None | V | call() |
Supplier<V> | None | V | get() |
Function<T,R> | T | R | apply(T) |
4.1. Which interface represents a no-input, no-output task?
4.2. Which two interfaces follow the no-input, value-output pattern?
4.3. What important difference exists between Callable and Supplier?
4.4. Which interface communicates most clearly that work is intended to produce a result through a concurrency API?
String url = "https://example.com";
Callable<String> task = () -> {
return fetchData(url);
};
5.1. How does the task receive its URL?
5.2. What does it mean that the lambda captures url?
5.3. What does “effectively final” mean?
5.4. Why might each HTTP request be represented by a separate Callable?
Future<Integer> future = executor.submit(task);
6.1. Is future the calculated integer?
6.2. Is the task guaranteed to be finished when submit() returns?
6.3. What does a Future<Integer> represent?
6.4. Why can submit() return before the final result exists?
Integer result = future.get();
7.1. What happens if the task has already finished?
7.2. What happens if the task is still running?
7.3. Which thread waits when get() is called?
7.4. Why is get() described as a blocking operation?
Compare these versions.
for (Callable<String> task : tasks) {
Future<String> future = executor.submit(task);
System.out.println(future.get());
}
List<Future<String>> futures = new ArrayList<>();
for (Callable<String> task : tasks) {
futures.add(executor.submit(task));
}
for (Future<String> future : futures) {
System.out.println(future.get());
}
8.1. Which version allows several tasks to execute concurrently?
8.2. Why is Version A mostly sequential?
8.3. In Version B, does waiting for the first future prevent other submitted tasks from running?
8.4. What general rule can be derived from this comparison?
future.isDone();
future.isCancelled();
9.1. What does isDone() report?
9.2. Does isDone() wait?
9.3. Is a task that failed with an exception considered done?
9.4. Why should we normally avoid repeatedly checking isDone() in a loop?
String result = future.get(5, TimeUnit.SECONDS);
10.1. What happens if the result is available within five seconds?
10.2. What happens if it is not available?
10.3. Does a TimeoutException automatically stop the task?
10.4. How is a future timeout different from an HTTP request timeout?
boolean cancelled = future.cancel(true);
11.1. What does cancel(true) attempt to do?
11.2. Is cancellation guaranteed to stop a running task immediately?
11.3. What happens if the task ignores interruption?
11.4. Can a successfully completed task normally be cancelled?
try {
String result = future.get();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} catch (ExecutionException ex) {
System.err.println(ex.getCause().getMessage());
}
12.1. What does InterruptedException mean here?
12.2. What does ExecutionException mean?
12.3. How can we access the exception originally thrown by the task?
12.4. Does one failed Future mean all submitted tasks failed?
Compare:
new Thread(task).start();
with:
executor.submit(task);
13.1. Who creates and manages worker threads when an ExecutorService is used?
13.2. Who decides which worker executes a submitted task?
13.3. What happens when all workers are busy?
13.4. Why is separating task submission from thread creation useful?
ExecutorService executor =
Executors.newFixedThreadPool(4);
14.1. How many tasks can normally execute simultaneously?
14.2. What happens if ten tasks are submitted?
14.3. Are four new threads created for every four tasks?
14.4. Why are worker threads reused?
executor.execute(runnable);
Future<?> future = executor.submit(runnable);
15.1. Which method returns a Future?
15.2. When might execute() be sufficient?
15.3. Why might submit() still be useful for a Runnable?
15.4. What result value does a successfully completed Runnable normally place in its Future?
ExecutorService executor =
Executors.newFixedThreadPool(3);
Callable<String> task = () -> "Hello";
Future<String> future = executor.submit(task);
String result = future.get();
16.1. Which object describes the work?
16.2. Which object manages execution?
16.3. Which object represents the eventual result?
16.4. Which method retrieves the result?
executor.shutdown();
17.1. Why might an application continue running if its executor is never shut down?
17.2. Does shutdown() immediately terminate running tasks?
17.3. What happens to tasks already waiting in the queue?
17.4. Can new tasks be submitted after shutdown?
executor.shutdown();
boolean finished = executor.awaitTermination(
30,
TimeUnit.SECONDS
);
18.1. Why is shutdown() called before awaitTermination()?
18.2. What does awaitTermination() wait for?
18.3. What does a returned value of false mean?
18.4. Does false mean that unfinished tasks were automatically cancelled?
List<Runnable> notStarted = executor.shutdownNow();
19.1. Does shutdownNow() guarantee immediate termination?
19.2. What happens to running tasks?
19.3. What does the returned list contain?
19.4. Why must running tasks respond correctly to interruption?
20.1. Should every program use the same number of worker threads?
20.2. How do CPU-bound and I/O-bound tasks differ?
20.3. Why might an HTTP application use more workers than a CPU-intensive calculation?
20.4. Why can too many concurrent requests be harmful?
Callable<String> task =
() -> fetchData("https://example.com");
Future<String> future = executor.submit(task);
21.1. Why are independent HTTP requests suitable executor tasks?
21.2. Why should one HttpClient normally be reused?
21.3. What happens if one request fails?
21.4. Why should both HTTP requests and future waiting have sensible time limits?
ExecutorService executor =
Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
executor.submit(task);
}
P1.1. How many tasks are submitted?
P1.2. How many tasks can normally execute simultaneously?
P1.3. What happens to the remaining tasks?
Future<Integer> future =
executor.submit(() -> 42);
System.out.println(future.isDone());
P2.1. Is the output guaranteed to be false?
P2.2. Why is the answer timing-dependent?
Future<String> future = executor.submit(() -> {
throw new IOException("Failure");
});
String result = future.get();
P3.1. Which method reports the task failure?
P3.2. Which exception wraps the original IOException?
P3.3. How can the original exception be accessed?
executor.shutdown();
executor.submit(task);
P4.1. Is the task accepted?
P4.2. Which exception should be expected?
Future<String> future = executor.submit(task);
executor.shutdown();
String result = future.get();
P5.1. Can the previously accepted task still complete?
P5.2. Can its result still be retrieved?
P5.3. What exactly does shutdown() prevent?
1.1. Runnable.run() returns no value because its return type is void.
1.2. It cannot receive the value directly from run(). The task would need to modify some external state, call another object, or be replaced by a result-producing abstraction such as Callable.
1.3. No. The run() method does not declare checked exceptions.
1.4. Several threads could access the shared variable concurrently, causing visibility problems, race conditions or corrupted data unless access is coordinated.
2.1. It has exactly one abstract method, call().
2.2. V is the type of value returned by the task.
2.3. call() returns a value and may throw checked exceptions. run() returns void and cannot declare checked exceptions.
2.4. Many useful operations, including file and network operations, can fail with checked exceptions. Callable allows the task to report those failures.
3.1. It receives no parameters.
3.2. It returns an Integer.
3.3. No. Creating the Callable only describes the calculation.
3.4. Yes:
Callable<Integer> task = () -> 20 + 22;
4.1. Runnable.
4.2. Callable<V> and Supplier<V>.
4.3. Callable.call() may declare checked exceptions; Supplier.get() cannot.
4.4. Callable<V>, particularly when submitted to an executor.
5.1. The lambda captures the local url variable from its surrounding scope.
5.2. The lambda retains access to that value when it executes later.
5.3. A variable is effectively final when it is assigned once and not reassigned, even if the final keyword is absent.
5.4. Each request is independent and produces its own result, making it suitable as a separate task.
6.1. No. It is an object representing access to an eventual result.
6.2. No. The task may be queued, running or already finished.
6.3. It represents an Integer result that may become available later, together with operations for checking, waiting and cancellation.
6.4. Submission schedules the work. Waiting for the result is a separate decision made by the caller.
7.1. get() returns the result immediately.
7.2. The calling thread waits until the task completes, fails or is cancelled.
7.3. The thread that calls get(), often the main thread.
7.4. It can pause the calling thread until the result is available.
8.1. Version B.
8.2. It submits one task and immediately waits for it before submitting the next task.
8.3. No. Other tasks have already been submitted, so executor workers can continue running them.
8.4. Submit independent tasks first, then collect their results.
9.1. It reports whether the task has completed normally, failed or been cancelled.
9.2. No. It returns immediately.
9.3. Yes. Failed and cancelled tasks are also considered done.
9.4. Repeated checking wastes CPU time and is a form of busy waiting. Use get(), a timed get() or another coordination mechanism.
10.1. get() returns the result.
10.2. It throws TimeoutException.
10.3. No. The underlying task may continue running.
10.4. A future timeout limits how long the caller waits. An HTTP timeout limits part of the network operation itself.
11.1. It attempts to cancel the task and may interrupt its worker if the task is already running.
11.2. No. Java interruption is cooperative, not forced termination.
11.3. The task may continue running despite the cancellation request.
11.4. No. Once completed, its result has already been produced.
12.1. The thread waiting in get() was interrupted.
12.2. The worker task completed by throwing an exception.
12.3. Call ex.getCause().
12.4. No. Each submitted task has its own completion and failure state.
13.1. The executor implementation creates and manages them.
13.2. The executor schedules the task on an available worker.
13.3. Additional tasks normally wait in the executor’s work queue.
13.4. It allows tasks and thread-management policy to change independently and prevents application code from manually managing every thread.
14.1. Up to four tasks.
14.2. Four can run while the remaining tasks wait in the queue.
14.3. No. The same four workers are reused.
14.4. Thread creation has a cost, and limiting the number of workers controls resource use.
15.1. submit().
15.2. When the caller only needs to start an action and does not need a handle for its completion.
15.3. Its Future can be used to wait, detect failure or request cancellation.
15.4. The result value is normally null, because Runnable.run() returns no value.
16.1. The Callable<String>.
16.2. The ExecutorService.
16.3. The Future<String>.
16.4. future.get().
17.1. Pool worker threads normally remain alive waiting for additional work and can keep the JVM running.
17.2. No. Previously accepted tasks are allowed to complete.
17.3. They remain accepted and are executed before termination.
17.4. No. New submissions are rejected, normally with RejectedExecutionException.
18.1. The executor must first be told to begin an orderly shutdown; otherwise it is still running and waiting for future submissions.
18.2. It waits for the executor to terminate after its accepted work has finished.
18.3. The timeout expired before termination completed.
18.4. No. It only means the wait timed out; tasks may still be running.
19.1. No. It makes a best-effort attempt.
19.2. Their threads are normally interrupted, but tasks must cooperate.
19.3. Tasks that were submitted but had not begun execution.
19.4. Interruption does not forcibly terminate Java code. The task must check or respond to interruption.
20.1. No. Pool size depends on the workload, machine and external constraints.
20.2. CPU-bound tasks spend most of their time calculating. I/O-bound tasks spend substantial time waiting.
20.3. While some workers wait for I/O, others can make progress. CPU-heavy tasks compete directly for a limited number of processor cores.
20.4. They can exhaust local resources, overload servers, trigger rate limits and increase latency.
21.1. They are often independent and spend significant time waiting for remote servers.
21.2. HttpClient is designed for reuse and can reuse connections and related resources.
21.3. Its Callable completes exceptionally, and its Future.get() reports an ExecutionException. Other tasks can still succeed.
21.4. Time limits prevent a request or caller from waiting indefinitely when a server or task does not complete.
P1.1. Ten tasks.
P1.2. Two tasks.
P1.3. They normally wait in the executor’s task queue until a worker becomes available.
P2.1. No. It could print either true or false.
P2.2. The worker may complete the very short task before the main thread calls isDone().
P3.1. future.get().
P3.2. ExecutionException.
P3.3. By calling executionException.getCause().
P4.1. No.
P4.2. RejectedExecutionException.
P5.1. Yes. An orderly shutdown allows accepted tasks to finish.
P5.2. Yes. Its Future remains usable.
P5.3. It prevents new task submissions and begins orderly executor termination.
Callable<V> describes work that returns V
ExecutorService schedules and executes the work
Future<V> represents the eventual result
- Use
Runnablefor work that returns no value. - Use
Callable<V>for work that returns a value or declares checked exceptions. - Submit tasks before calling
Future.get()when the tasks should run concurrently. - Always shut down an
ExecutorServicewhen it is no longer needed. - Treat interruption, task failure and cancellation as separate situations.