JAVA 21 FEATURES
Executor Framework - Key Classes & Methods
Rule: Understand the relationship between Executor, ExecutorService, and Executors utility class.
Class Hierarchy:
Executor
interface → executesRunnable
onlyExecutorService
interface → extends Executor, executes bothRunnable
andCallable
Executors
utility class → provides static factory methods (all start with “new”)
Essential Executors Factory Methods:
import java.util.concurrent.*;
// Fixed thread pool - reuses fixed number of threads
ExecutorService fixed = Executors.newFixedThreadPool(4);
// Single thread executor - sequential execution
ExecutorService single = Executors.newSingleThreadExecutor();
// Cached thread pool - creates threads as needed, reuses idle ones
ExecutorService cached = Executors.newCachedThreadPool();
// Scheduled executors for delayed/periodic tasks
ScheduledExecutorService singleScheduled = Executors.newSingleThreadScheduledExecutor();
ScheduledExecutorService multiScheduled = Executors.newScheduledThreadPool(3);
// Virtual threads (Java 21) - lightweight threads
ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor();
Practical Example:
// Basic Executor - only Runnable
Executor basicExecutor = Executors.newSingleThreadExecutor();
basicExecutor.execute(() -> System.out.println("Running task"));
// ExecutorService - both Runnable and Callable
ExecutorService service = Executors.newFixedThreadPool(2);
// Execute Runnable
service.execute(() -> System.out.println("Runnable task"));
// Submit Callable
Future<String> result = service.submit(() -> {
Thread.sleep(1000);
return "Callable result";
});
try {
System.out.println(result.get()); // "Callable result"
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
Thread Pool Comparison:
// newFixedThreadPool(4) - exactly 4 threads, queues excess tasks
ExecutorService fixed = Executors.newFixedThreadPool(4);
// newCachedThreadPool() - creates threads as needed, kills idle ones after 60s
ExecutorService cached = Executors.newCachedThreadPool();
// newSingleThreadExecutor() - exactly 1 thread, tasks execute sequentially
ExecutorService single = Executors.newSingleThreadExecutor();
// newVirtualThreadPerTaskExecutor() - creates new virtual thread per task
ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor();
Common Pitfalls:
// Pitfall 1: Trying to submit Callable to basic Executor
Executor executor = Executors.newSingleThreadExecutor();
// executor.submit(() -> "result"); // ❌ Executor doesn't have submit()
// Pitfall 2: Forgetting shutdown
ExecutorService service = Executors.newFixedThreadPool(2);
service.submit(() -> "task");
// Missing service.shutdown(); // ❌ JVM won't terminate
// Pitfall 3: Wrong method name
// Executors.createFixedThreadPool(4); // ❌ No such method
ExecutorService correct = Executors.newFixedThreadPool(4); // ✅
💡 Learning Tip: “NEW executors create, SUBMIT callables” - Executors factory methods start with ‘new’, ExecutorService can ‘submit’ Callables (not basic Executor).
Q: What’s the difference between Executor and ExecutorService interfaces?
A: Executor only executes Runnable with execute(), while ExecutorService extends Executor and can submit both Runnable and Callable, returning Future objects.