Callable, Future & ExecutorService
Returning values from threads and managing pools — the standard approach for production code.
Why ExecutorService instead of raw threads?
Creating a raw Thread per task is wasteful — threads are expensive OS resources. ExecutorService manages a thread pool: a fixed set of threads that pick up tasks from a queue.
Runnable tasks return nothing. Callable<V> is the equivalent that returns a value:
Callable<Integer> task = () -> {
Thread.sleep(100);
return 42;
};
Submitting and getting the result:
ExecutorService pool = Executors.newFixedThreadPool(4);
Future<Integer> future = pool.submit(task); // returns immediately
// … do other work …
int result = future.get(); // BLOCKS until the task finishes
pool.shutdown(); // must call — prevents resource leak
Future API highlights:
| Method | Behaviour |
|---|---|
| get() | block until done; throws ExecutionException if the task threw |
| get(3, TimeUnit.SECONDS) | block with a timeout; throws TimeoutException |
| isDone() | non-blocking poll — true when finished |
| cancel(true) | attempt to interrupt the running task |
shutdown() vs shutdownNow():
- shutdown() — no new tasks accepted; waits for in-flight tasks to finish.
- shutdownNow() — interrupts running tasks and returns the queued ones.
Always call shutdown() in a finally block or use a try-with-resources shim so the pool is not left running forever.