Creating Threads Using Executors

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

  1. Implement a Runnable or Callable task (i.e., what you want the thread to execute).
  2. Create an ExecutorService (like a thread pool).
  3. Submit the task to the Executor.
  4. 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).
Scroll to Top