In Java, managing threads manually (by creating a Thread object for each task) can quickly become messy and inefficient when applications grow.
To solve this, Java introduced the Executor Framework in the java.util.concurrent package — a powerful way to manage and control thread execution more efficiently and cleanly.
Instead of manually starting threads, Executor Framework provides a pool of threads and reuses them to execute tasks, which reduces overhead and improves performance.
Steps to Create Threads Using Executor Framework
- Implement a Runnable or Callable task (i.e., what you want the thread to execute).
- Create an ExecutorService (like a thread pool).
- Submit the task to the Executor.
- Shutdown the Executor when tasks are complete.
Sample Implementation
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorExample {
public static void main(String[] args) {
// Step 1: Create an ExecutorService (Thread pool)
ExecutorService executor = Executors.newFixedThreadPool(3); // 3 threads in pool
// Step 2: Create and submit Runnable tasks
for (int i = 1; i <= 5; i++) {
Runnable task = new WorkerThread("Task " + i);
executor.execute(task);
}
// Step 3: Shutdown the executor
executor.shutdown();
System.out.println("All tasks submitted...");
}
}
class WorkerThread implements Runnable {
private String message;
public WorkerThread(String message) {
this.message = message;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " (Start) message = " + message);
processMessage();
System.out.println(Thread.currentThread().getName() + " (End)");
}
private void processMessage() {
try {
Thread.sleep(2000); // Simulating time-consuming task
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}Code language: JavaScript (javascript)
Creating Multiple Threads using ExecutorService
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class```java
public class SimpleExecutorExample {
public static void main(String[] args) {
// Create a thread pool with 2 threads
ExecutorService executor = Executors.newFixedThreadPool(2);
// Submit 4 simple tasks
for (int i = 1; i <= 4; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " running on " + Thread.currentThread().getName());
try {
Thread.sleep(500); // Simulate work
} catch (InterruptedException e) {
System.out.println("Task " + taskId + " interrupted");
}
System.out.println("Task " + taskId + " done");
});
}
// Shutdown the executor
executor.shutdown();
}
}
/*
Task 1 running on pool-1-thread-1
Task 2 running on pool-1-thread-2
Task 1 done
Task 3 running on pool-1-thread-1
Task 2 done
Task 4 running on pool-1-thread-2
Task 3 done
Task 4 done
*/Constructors and Methods of ExecutorServiceÂ
| Name | Description |
|---|---|
| ThreadPoolExecutor(int corePoolSize, int maxPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) | Creates a thread pool with core/max pool sizes, keep-alive time, and task queue. |
| ThreadPoolExecutor(int corePoolSize, int maxPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) | Adds a custom thread factory for thread creation. |
| ThreadPoolExecutor(int corePoolSize, int maxPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) | Adds a rejection handler for tasks when queue is full. |
| Executors.newFixedThreadPool(int nThreads) | Creates a fixed-size thread pool. |
| Executors.newCachedThreadPool() | Creates a pool that reuses idle threads and creates new ones as needed. |
| Executors.newSingleThreadExecutor() | Creates a single-threaded executor for sequential task processing. |
| void execute(Runnable command) | Runs a Runnable task asynchronously without a result. |
| <T> Future<T> submit(Callable<T> task) | Submits a Callable task, returns a Future for the result. |
| Future<?> submit(Runnable task) | Submits a Runnable task, returns a Future to track completion. |
| <T> Future<T> submit(Runnable task, T result) | Submits a Runnable task with a specified result, returns a Future. |
| void shutdown() | Initiates graceful shutdown, allowing running tasks to finish. |
| List<Runnable> shutdownNow() | Stops all tasks immediately, returns unstarted tasks. |
| boolean isShutdown() | Returns true if shutdown has been initiated. |
| boolean isTerminated() | Returns true if all tasks have completed after shutdown. |
| boolean awaitTermination(long timeout, TimeUnit unit) | Blocks until tasks finish or timeout expires, returns true if terminated. |
| <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) | Executes all tasks, returns Futures for results (blocks until all complete). |
| <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) | Executes all tasks with a timeout, returns Futures for completed tasks. |
| <T> T invokeAny(Collection<? extends Callable<T>> tasks) | Executes tasks, returns result of one that completes successfully (blocks until one completes). |
| <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) | Executes tasks with a timeout, returns result of one that completes successfully. |
Why Use ExecutorService?
- Simplifies thread management compared to manual Thread creation.
- Reuses threads via a pool, reducing overhead.
- Provides flexible task submission (Runnable or Callable) and result tracking.
- Built-in lifecycle management (shutdown, termination).
