← Back to Advanced Java Course | Chapter 2: Multithreading | Lesson 6 of 8

Java Executor Framework

The Executor Framework is a manager that hands out jobs to worker threads, like a boss giving tasks to a team. You submit the work and it decides who does it.
Syntax
java
ExecutorService executor = Executors.newFixedThreadPool(n);
executor.submit(task);
executor.shutdown();

What is the Executor Framework?

The Executor framework is a built-in concurrency library introduced in Java 5 that manages thread pools, task queues, and scheduling automatically. It removes the need to manually create, start, and track Thread objects yourself, which becomes error-prone once an application needs more than a handful of concurrent tasks.

Example: What is the Executor Framework?

java
// Import java.util.concurrent.ExecutorService so it can be used by its short name
import java.util.concurrent.ExecutorService;
// Import java.util.concurrent.Executors so it can be used by its short name
import java.util.concurrent.Executors;
// Define the class Main
public class Main {
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a thread pool to run tasks
		ExecutorService executor = Executors.newFixedThreadPool(2);
		executor.submit(() -> System.out.println("Task managed by the pool"));
		executor.shutdown();
	}
}

Single Thread Executor

A Single Thread Executor manages exactly one background worker thread and executes every submitted task sequentially, strictly in the order those tasks were submitted. This gives you the safety of single-threaded execution — no race conditions between tasks — while still keeping that work off the calling thread.

Example: Single Thread Executor

java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		executor.submit(() -> System.out.println("Task 1"));
		executor.submit(() -> System.out.println("Task 2")); // runs after Task 1, same worker
		executor.shutdown();
	}
}

Fixed Thread Pool

A Fixed Thread Pool manages a set number of worker threads and hands submitted tasks to whichever thread is free, reusing each thread across many tasks instead of spawning a new one every time. This bounds resource usage predictably, which matters a lot under heavy or bursty load.

Example: Fixed Thread Pool

java
// Import java.util.concurrent.ExecutorService so it can be used by its short name
import java.util.concurrent.ExecutorService;
// Import java.util.concurrent.Executors so it can be used by its short name
import java.util.concurrent.Executors;
// Define the class Main
public class Main {
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a thread pool to run tasks
		ExecutorService executor = Executors.newFixedThreadPool(3);
		// Loop: repeat while the condition holds
		for (int i = 1; i <= 5; i++) {
			// Declare taskId and set it to i
			int taskId = i;
			executor.submit(() -> System.out.println("Task " + taskId));
		}
		executor.shutdown();
	}
}

Submitting Callable Tasks

Unlike the Runnable interface, the Callable interface represents tasks that can return a computed result and can throw checked exceptions during execution. Submitting a Callable to an executor gets you back a Future you can use to retrieve that result once the task finishes.

Example: Submitting Callable Tasks

java
// Import java.util.concurrent.* so it can be used by its short name
import java.util.concurrent.*;
// Define the class Main
public class Main {
	// Program entry point: the JVM starts running here
	public static void main(String[] args) throws Exception {
		// Create a thread pool to run tasks
		ExecutorService executor = Executors.newSingleThreadExecutor();
		Callable<Integer> task = () -> 10 + 20;
		Future<Integer> future = executor.submit(task);
		// Print a line to the console
		System.out.println(future.get());
		executor.shutdown();
	}
}

Shutting Down Executors

Always explicitly shut down executors you create, since their worker threads would otherwise keep the JVM alive indefinitely. Call shutdown() to stop accepting new tasks while letting already-submitted ones finish normally, or shutdownNow() when you need everything to stop immediately.

Example: Shutting Down Executors

java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newFixedThreadPool(2);
		executor.submit(() -> System.out.println("Task running"));
		executor.shutdown(); // stop accepting new tasks, finish existing ones
	}
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Forgetting to call shutdown() on the executor, so its non-daemon worker threads keep the JVM from exiting.
  2. Calling future.get() right after each submit, which makes the tasks run one after another instead of in parallel.
  3. Using submit with a Runnable that throws and never checking the Future, since the exception is stored in the Future and silently lost.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.