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).