Executor Framework in Java

Naveen AnamaneniUpdated · 5 min read

The Executor framework, in the java.util.concurrent package, is used to run tasks on a pool of reusable threads instead of creating a new Thread for every task. You hand a task (a Runnable or Callable) to an executor, and it decides which thread runs it and when. A thread pool keeps a set of worker threads alive and gives each new task to a free worker, which saves the cost of creating threads and limits how many run at once. In this lesson, you will learn the main interfaces and how to submit tasks.

Why Use a Thread Pool?

Creating threads by handUsing an executor
A new thread for every task (slow to create)Threads are created once and reused
No limit: 10,000 tasks can mean 10,000 threadsA fixed number of workers; extra tasks wait in a queue
You manage starting, joining and errorsThe executor manages threads; you get Future results
Hard to shut down cleanlyshutdown() and awaitTermination()
Tasks wait in a queue; a fixed set of worker threads takes them one by one

The Main Types

TypeDescription
ExecutorIt is used to run a Runnable: one method, execute(Runnable).
ExecutorServiceIt extends Executor with submit, invokeAll, invokeAny, shutdown and more.
ScheduledExecutorServiceIt runs tasks after a delay or repeatedly.
ExecutorsA utility class with factory methods such as newFixedThreadPool(n).
Future<V>It represents the result of a submitted task.

Methods of ExecutorService

MethodDescription
execute(Runnable task)It runs a task with no result.
submit(Runnable or Callable)It runs a task and returns a Future.
invokeAll(tasks)It runs all tasks and waits for all results.
invokeAny(tasks)It returns the result of the first task to succeed.
shutdown()It stops accepting new tasks; queued tasks still run.
awaitTermination(time, unit)It waits until all tasks finish or the time runs out.
close()It shuts down and waits (Java 19; used with try-with-resources).

Example: Six Tasks on Three Threads

Let's see an example that runs six tasks on a pool of three worker threads.

PoolDemo.java▶ Try it
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class PoolDemo {
    public static void main(String[] args) {
        Set<String> workers = ConcurrentHashMap.newKeySet();
        long start = System.nanoTime();
 
        try (ExecutorService pool = Executors.newFixedThreadPool(3)) {
            for (int i = 1; i <= 6; i++) {
                int task = i;
                pool.submit(() -> {
                    workers.add(Thread.currentThread().getName());
                    sleep(200);
                    return task;
                });
            }
        }
 
        long ms = (System.nanoTime() - start) / 1_000_000;
        System.out.println("Different worker threads used: " + workers.size());
        System.out.println("Took about two rounds (400-600 ms)? "
            + (ms >= 400 && ms < 600));
    }
 
    static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Output
Different worker threads used: 3
Took about two rounds (400-600 ms)? true

Explanation: Executors.newFixedThreadPool(3) creates a pool with three worker threads. Six tasks are submitted; three run at once and the other three wait in the queue, so the work takes two rounds of 200 milliseconds. Only three threads were ever created, and each was reused. Since Java 19, ExecutorService is AutoCloseable, so try-with-resources shuts the pool down and waits for the tasks to finish.

Sizing a Pool

Common Mistakes

Forgetting to shut down the executor. Its non-daemon threads keep the program alive.

Creating a new executor for every task. Create one and reuse it.

Ignoring the Future from submit. An exception thrown by the task is hidden inside it until you call get().

Interview Corner

What is the Executor framework, and why use it?

The Executor framework in java.util.concurrent separates task submission from task execution. You submit Runnable or Callable tasks to an ExecutorService, typically created by the Executors factory methods, and it runs them on a managed pool of reusable threads. Thread pools avoid the cost of creating threads for every task, bound the number of concurrent threads, queue excess work, return results and exceptions through Future, and support orderly shutdown.

Practice

  1. Easy: Submit five tasks that each print their task number and thread name to a pool of two threads.
  2. Harder: Use invokeAll to compute the squares of the numbers 1 to 10 in a pool of four threads, and print the sum of the results.

Quick check: Executor framework

4 questions
1What does a thread pool do?
2Which method returns a Future?
3What happens to extra tasks when all workers are busy?
4Why must an executor be shut down?

Key Takeaways

Was this tutorial helpful?

Lesson 411 of 482