Java's Callable
and Future
are powerful tools for multithreaded programming, especially when you need to get a result back from a thread. Unlike Runnable
, which cannot return a result or throw a checked exception, Callable
enables developers to submit tasks that produce values.
This tutorial dives deep into how Callable
and Future
work, where they fit in the multithreading landscape, real-world use cases, performance considerations, and how to use them effectively in Java 8 through Java 21+.
1. Introduction to Multithreading
Multithreading allows concurrent execution of two or more parts of a program for maximum CPU utilization. Java supports multithreading natively, helping you write high-performance applications.
Real-world importance:
- Parallel file processing
- Background computations in GUIs
- Network request handling in web servers
2. Thread Lifecycle in Java
NEW
→RUNNABLE
→RUNNING
→BLOCKED
→TERMINATED
- Methods like
start()
,run()
,join()
influence these transitions.
3. Callable vs Runnable
Feature | Runnable | Callable |
---|---|---|
Return value | No | Yes |
Exception | Cannot throw | Can throw checked |
Interface method | run() |
call() |
Example: Using Callable
Callable<Integer> task = () -> {
return 42;
};
4. Getting Results with Future
A Future
represents the result of an asynchronous computation.
Example:
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
TimeUnit.SECONDS.sleep(1);
return 123;
});
System.out.println("Result: " + future.get()); // blocks until result is available
5. ExecutorService and Thread Pools
Thread pools improve performance by reusing threads.
Executors.newFixedThreadPool(5)
Executors.newCachedThreadPool()
Executors.newScheduledThreadPool()
6. Java Memory Model and volatile
- Ensures visibility and ordering of variables across threads.
- Use
volatile
when multiple threads update a single variable.
7. Synchronization Tools
join()
: Waits for thread completion.wait()
/notify()
: Intrinsic locks for coordination.sleep()
: Pauses execution.
8. Locking Strategies
synchronized
: Basic lockingReentrantLock
: More control, tryLock, fairnessStampedLock
: For optimistic readsReadWriteLock
: For high-read/low-write
9. Advanced Concurrency Classes
ConcurrentHashMap
: High-performance thread-safe mapsBlockingQueue
: Used in producer-consumer scenariosForkJoinPool
: For divide-and-conquer parallelismCompletableFuture
: Async programming with pipelines
10. Real-world Use Cases
- Producer–Consumer with
BlockingQueue
- Thread pool for web server handling
- Batch file processing using
invokeAll()
11. Best Practices and Anti-patterns
✅ Do
- Use
ExecutorService
for thread reuse - Handle exceptions properly in
Future.get()
❌ Avoid
- Blocking the main thread unnecessarily
- Creating threads manually for short tasks
12. Multithreading Design Patterns
- Worker Thread: Processes tasks from a queue
- Future Task: Wrapper for async execution
- Thread-per-message: One thread per request
13. 📌 What’s New in Java [version]?
Java 8
- Lambdas for
Runnable
CompletableFuture
- Parallel Streams
Java 9
- Flow API (Reactive Streams)
Java 11+
- Minor improvements in
CompletableFuture
- Cleaner APIs
Java 21
- Virtual Threads (Project Loom)
- Structured Concurrency
- Scoped Values
14. Conclusion and Key Takeaways
Callable
and Future
simplify result-handling in multithreaded code, offering better performance, exception handling, and scalability when used with thread pools and executor frameworks.
Key Takeaways:
- Prefer
Callable
when you need results - Always shut down the
ExecutorService
- Embrace
CompletableFuture
for complex pipelines - Use structured concurrency (Java 21+) for clarity
15. FAQ
Q1: Why not call run()
directly on a thread?
A: That runs it on the current thread. Use start()
to run it concurrently.
Q2: What happens if Future.get()
is called before the task is done?
A: It blocks until the result is available.
Q3: What’s the difference between invokeAll()
and submit()
?
A: invokeAll()
waits for all tasks to finish. submit()
is used per-task.
Q4: Can Callable
throw checked exceptions?
A: Yes! Unlike Runnable
.
Q5: What if the thread pool is full?
A: Tasks are queued or rejected based on policy.
Q6: What’s false sharing in threads?
A: Cache contention due to nearby variables on same cache line.
Q7: How does CompletableFuture
differ from Future
?
A: It's non-blocking and supports chaining.
Q8: When should I use volatile
?
A: When multiple threads read/write a simple shared variable.
Q9: Are synchronized
and ReentrantLock
interchangeable?
A: Mostly yes, but ReentrantLock
provides more control.
Q10: Are virtual threads production-ready?
A: Yes, from Java 21 onwards with Project Loom.