ExecutorService Interface
ExecutorService Interface
ExecutorService is a high-level API for managing thread pools and asynchronous execution.
import java.util.concurrent.*;
public class ExecutorServiceDemo {
public static void main(String[] args) {
// Create a fixed thread pool
ExecutorService executor = Executors.newFixedThreadPool(4);
// Submit Runnable tasks
executor.submit(() -> {
System.out.println("Task 1 on: " + Thread.currentThread().getName());
});
executor.submit(() -> {
System.out.println("Task 2 on: " + Thread.currentThread().getName());
});
// Always shutdown!
executor.shutdown();
try {
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
Key methods:
submit(Runnable/Callable)- Returns Futureexecute(Runnable)- Fire and forgetinvokeAll(tasks)- Wait for allinvokeAny(tasks)- Wait for first successshutdown()- Stop accepting new tasksshutdownNow()- Interrupt all tasksawaitTermination()- Wait for completion
Thread Pool Types
Thread Pool Types
1. Fixed Thread Pool
// Fixed number of threads
ExecutorService fixed = Executors.newFixedThreadPool(4);
// 4 threads, tasks queue when all busy
2. Cached Thread Pool
// Creates threads as needed, reuses idle threads
ExecutorService cached = Executors.newCachedThreadPool();
// No limit on threads (dangerous for large loads)
// Threads expire after 60 seconds idle
3. Single Thread Executor
// Single worker thread
ExecutorService single = Executors.newSingleThreadExecutor();
// Tasks execute sequentially in FIFO order
4. Scheduled Thread Pool
// For delayed/periodic tasks
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(4);
// Run after 2 seconds
scheduled.schedule(() -> {
System.out.println("Delayed task");
}, 2, TimeUnit.SECONDS);
// Run every 5 seconds
scheduled.scheduleAtFixedRate(() -> {
System.out.println("Periodic task");
}, 0, 5, TimeUnit.SECONDS);
5. Custom Thread Pool
ThreadPoolExecutor custom = new ThreadPoolExecutor(
2, // core pool size
4, // max pool size
60L, TimeUnit.SECONDS, // keep alive time
new LinkedBlockingQueue<>(100) // work queue
);
When to use which:
- Fixed: CPU-bound tasks, controlled parallelism
- Cached: Many short-lived tasks
- Single: Sequential processing, resource constraints
- Scheduled: Delays, periodic tasks
Callable and Future
Callable and Future
Callable is like Runnable but returns a value. Future represents the result.
import java.util.concurrent.*;
public class CallableFutureDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
// Callable returns a value
Callable<Integer> task = () -> {
Thread.sleep(2000); // Simulate work
return 42;
};
// Submit and get Future
Future<Integer> future = executor.submit(task);
// Do other work while task runs...
System.out.println("Doing other work...");
// Get result (blocks until done)
Integer result = future.get(); // Blocks for 2 seconds
System.out.println("Result: " + result); // 42
executor.shutdown();
}
}
Future methods:
Future<Integer> future = executor.submit(task);
// Check if done
if (future.isDone()) {
Integer result = future.get();
}
// Get with timeout
try {
Integer result = future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true); // Cancel if taking too long
}
// Cancel
boolean cancelled = future.cancel(true); // true = interrupt
boolean isCancelled = future.isCancelled();
Callable with lambda:
Callable<String> task = () -> {
return "Hello from " + Thread.currentThread().getName();
};
Future<String> future = executor.submit(task);
System.out.println(future.get());
Common Patterns
Common ExecutorService Patterns
Pattern 1: Process List in Parallel
public <T> List<T> processInParallel(List<T> items, Function<T, T> processor) {
ExecutorService executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
List<Future<T>> futures = items.stream()
.map(item -> executor.submit(() -> processor.apply(item)))
.collect(Collectors.toList());
List<T> results = new ArrayList<>();
for (Future<T> future : futures) {
try {
results.add(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
executor.shutdown();
return results;
}
Pattern 2: Race Multiple Tasks
// Use invokeAny() - returns first successful result
List<Callable<String>> tasks = Arrays.asList(
() -> fetchFromAPI1(),
() -> fetchFromAPI2(),
() -> fetchFromDatabase()
);
String result = executor.invokeAny(tasks); // First to finish wins
Pattern 3: Batch Processing
List<List<Item>> batches = partition(items, 100);
List<Future<Void>> futures = batches.stream()
.map(batch -> executor.submit(() -> processBatch(batch)))
.collect(Collectors.toList());
// Wait for all batches
for (Future<Void> future : futures) {
future.get();
}
Pattern 4: Rate Limiting
Semaphore semaphore = new Semaphore(10); // Max 10 concurrent
for (Task task : tasks) {
executor.submit(() -> {
semaphore.acquire();
try {
process(task);
} finally {
semaphore.release();
}
});
}
Shutdown Strategies
Shutdown and awaitTermination
Graceful Shutdown:
ExecutorService executor = Executors.newFixedThreadPool(4);
// Submit tasks
for (int i = 0; i < 100; i++) {
executor.submit(() -> doWork());
}
// Shutdown: stop accepting new tasks, let running finish
executor.shutdown();
// Wait for completion
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // Force shutdown
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
Shutdown methods:
shutdown()- No new tasks, let current finishshutdownNow()- Interrupt all tasks, return pendingisShutdown()- Check if shutdown calledisTerminated()- Check if all tasks done
Best practices:
- Always call
shutdown()orshutdownNow() - Use
awaitTermination()to wait for completion - Handle
InterruptedExceptionproperly - Use try-with-resources for ExecutorService
- Don't use
Executors.newCachedThreadPool()in production
Practice Problems
Use ExecutorService to calculate the sum of a large array in parallel. Split the array into chunks and sum each chunk in a separate thread.
Solution
```java
import java.util.concurrent.*;
import java.util.*;
public class ParallelSum {
public static int parallelSum(int[] arr) throws Exception {
int numThreads = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
int chunkSize = arr.length / numThreads;
List<Future<Integer>> futures = new ArrayList<>();
for (int i = 0; i < numThreads; i++) {
int start = i * chunkSize;
int end = (i == numThreads - 1) ? arr.length : start + chunkSize;
final int[] chunk = Arrays.copyOfRange(arr, start, end);
futures.add(executor.submit(() -> {
int sum = 0;
for (int val : chunk) sum += val;
return sum;
}));
}
int total = 0;
for (Future<Integer> f : futures) {
total += f.get();
}
executor.shutdown();
return total;
}
}
```Implement a method that runs a task with a timeout. If the task doesn't complete within the timeout, cancel it and return a default value.
Solution
```java
import java.util.concurrent.*;
public class TimeoutTask {
public static <T> T runWithTimeout(Callable<T> task, T defaultValue, long timeoutMs) {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future<T> future = executor.submit(task);
return future.get(timeoutMs, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
return defaultValue;
} catch (Exception e) {
return defaultValue;
} finally {
executor.shutdownNow();
}
}
// Usage
public static void main(String[] args) {
String result = runWithTimeout(
() -> { Thread.sleep(5000); return "done"; },
"timeout",
1000 // 1 second timeout
);
System.out.println(result); // "timeout"
}
}
```Quiz
1. What is the difference between submit() and execute()?
2. What happens when you call shutdown() on ExecutorService?
3. When should you use invokeAny()?
4. What is the primary purpose of ExecutorService and Thread Pools?
Flashcards
Question
What is the difference between Callable and Runnable?
Click to reveal answer
Answer
Callable returns a value and can throw exceptions. Runnable returns void and cannot throw checked exceptions.
Question
What are the three ExecutorService shutdown methods?
Click to reveal answer
Answer
shutdown() - stop accepting, let finish. shutdownNow() - interrupt all. awaitTermination() - wait for completion.
Question
When should you use newCachedThreadPool()?
Click to reveal answer
Answer
Only for many short-lived tasks. Not recommended for production due to unbounded thread creation.
Question
What is ExecutorService and Thread Pools?
Click to reveal answer
Answer
ExecutorService and Thread Pools is a key concept in Java programming.
Question
When to use ExecutorService and Thread Pools?
Click to reveal answer
Answer
Use ExecutorService and Thread Pools when building production systems that require reliability, scalability, and maintainability.
Revision Notes
Key Takeaways
- 1.submit() returns Future, execute() doesn't
- 2.Always call shutdown() and awaitTermination()
- 3.Callable returns values, Runnable doesn't
- 4.invokeAny() is useful for redundant API calls
Interview Tips
- •Explain the difference between submit() and execute()
- •Know the shutdown sequence: shutdown() → awaitTermination() → shutdownNow()
- •Explain Future's get() with timeout pattern
- •Discuss when to use each thread pool type
Cheat Sheet
ExecutorService Cheat Sheet
Thread Pools
- FixedThreadPool(n): n threads
- CachedThreadPool: unlimited threads
- SingleThreadExecutor: 1 thread
- ScheduledThreadPool: delayed/periodic
Key Methods
- submit(Runnable/Callable) → Future
- execute(Runnable) → void
- invokeAll() → wait for all
- invokeAny() → first success
Shutdown
- shutdown(): stop accepting
- shutdownNow(): interrupt all
- awaitTermination(): wait for done
Future
- get(): blocks for result
- get(timeout): blocks with timeout
- cancel(true): interrupt task
- isDone(): check completion