Run Tasks with ExecutorService in Java
Problem
An ExecutorService manages a pool of reusable threads; you submit tasks to it and receive a Future, a handle you can use later to wait for and read each task's result.
Compute the factorials of 10 to 14 concurrently on a pool of three threads and print them in order.
Java Program
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorServiceExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(3); // 3 reusable worker threads
List<Future<Long>> results = new ArrayList<>();
for (int n = 10; n <= 14; n++) {
final int value = n;
results.add(pool.submit(() -> factorial(value))); // returns immediately with a Future
}
int n = 10;
for (Future<Long> f : results) {
System.out.println(n++ + "! = " + f.get()); // get() waits for that task's result
}
pool.shutdown(); // stop accepting tasks; lets the JVM exit once workers are idle
}
static long factorial(int n) {
long result = 1;
for (int i = 2; i <= n; i++) result *= i;
return result;
}
}Output
Core Logic
Tasks are submitted up front and run on whichever pool thread is free; reading the Futures in submission order gives ordered output no matter which task finishes first.
- 1
Executors.newFixedThreadPool(3)creates three worker threads that are reused for all tasks. - 2
pool.submit(() -> factorial(value))queues aCallableand immediately returns aFuture<Long>. - 3
f.get()waits for that particular result, so the loop prints 10! through 14! in order. - 4
pool.shutdown()stops the pool accepting work, so the JVM can exit when the threads go idle.
Key Point: Forgetting shutdown() leaves the pool's non-daemon threads alive, and the program never exits.
Key Concepts
Approach 2: Using invokeAll()
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorInvokeAll {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(
() -> "orders checked",
() -> "invoices sent",
() -> "report built");
List<Future<String>> done = pool.invokeAll(tasks); // blocks until every task has finished
for (Future<String> f : done) {
System.out.println(f.get());
}
pool.shutdown();
}
}
Output
Core Logic
invokeAll submits a whole batch of tasks and returns only when every one of them has completed.
- 1Three
Callable<String>tasks are placed in a list. - 2
pool.invokeAll(tasks)blocks until all have finished. - 3The returned Futures are in the same order as the input list, so results print predictably.
Key Point: Use invokeAll when you need every result before continuing, and invokeAny when the first successful result is enough.
Key Concepts
Interview Questions
What's the difference between submit() and execute()?
execute runs a Runnable and returns nothing. submit accepts a Runnable or Callable and returns a Future, which also captures any exception the task throws.
What's the difference between shutdown() and shutdownNow()?
shutdown stops new tasks but lets queued ones finish. shutdownNow interrupts running tasks and returns the ones that never started.