ExecutorService in Java

Introduction

The ExecutorService interface in Java is part of the java.util.concurrent package and provides a framework for managing a pool of threads and executing tasks asynchronously. It decouples task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. ExecutorService is typically used to manage and control the execution of tasks, making it easier to handle multithreading in Java.

Table of Contents

  1. Overview of ExecutorService
  2. Creating an ExecutorService
  3. Submitting Tasks to ExecutorService
  4. Shutting Down ExecutorService
  5. Example: Using ExecutorService
  6. Handling Callable and Future with ExecutorService
  7. Example: ScheduledExecutorService
  8. Conclusion

1. Overview of ExecutorService

ExecutorService is a subinterface of Executor that provides methods to manage the lifecycle of threads and to perform asynchronous task execution. Some of the key methods include:

  • submit(): Submits a task for execution and returns a Future representing the task.
  • invokeAll(): Executes a collection of tasks and returns a list of Future objects.
  • invokeAny(): Executes a collection of tasks and returns the result of one of the tasks.
  • shutdown(): Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.
  • shutdownNow(): Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.

2. Creating an ExecutorService

You can create an ExecutorService using the Executors factory methods, such as:

  • newFixedThreadPool(int nThreads): Creates a thread pool with a fixed number of threads.
  • newCachedThreadPool(): Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available.
  • newSingleThreadExecutor(): Creates an executor that uses a single worker thread.

Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceExample {
    public static void main(String[] args) {
        // Creating a fixed thread pool with 2 threads
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // Submit tasks to the executor service
        executorService.submit(() -> System.out.println("Task 1 executed by " + Thread.currentThread().getName()));
        executorService.submit(() -> System.out.println("Task 2 executed by " + Thread.currentThread().getName()));
        executorService.submit(() -> System.out.println("Task 3 executed by " + Thread.currentThread().getName()));

        // Shutdown the executor service
        executorService.shutdown();
    }
}

Output:

Task 1 executed by pool-1-thread-1
Task 2 executed by pool-1-thread-2
Task 3 executed by pool-1-thread-1

Explanation:

  • A fixed thread pool with 2 threads is created.
  • Three tasks are submitted to the executor service.
  • The tasks are executed by the threads in the pool.
  • The executor service is shut down.

3. Submitting Tasks to ExecutorService

Tasks can be submitted to the ExecutorService using the submit method. You can submit Runnable or Callable tasks.

Example:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SubmitTasksExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // Submitting a Runnable task
        Future<?> future1 = executorService.submit(() -> {
            System.out.println("Runnable task executed by " + Thread.currentThread().getName());
        });

        // Submitting a Callable task
        Future<String> future2 = executorService.submit(() -> {
            System.out.println("Callable task executed by " + Thread.currentThread().getName());
            return "Result from Callable task";
        });

        try {
            // Getting the result of the Callable task
            String result = future2.get();
            System.out.println(result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        executorService.shutdown();
    }
}

Output:

Runnable task executed by pool-1-thread-1
Callable task executed by pool-1-thread-2
Result from Callable task

Explanation:

  • A fixed thread pool with 2 threads is created.
  • A Runnable task and a Callable task are submitted to the executor service.
  • The Runnable task does not return a result.
  • The Callable task returns a result which is retrieved using the Future object.

4. Shutting Down ExecutorService

It's important to shut down the ExecutorService to release the resources it holds. You can shut it down gracefully or forcefully.

Graceful Shutdown:

executorService.shutdown();
try {
    if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
        executorService.shutdownNow();
    }
} catch (InterruptedException e) {
    executorService.shutdownNow();
}

Forceful Shutdown:

executorService.shutdownNow();

5. Example: Using ExecutorService

Let's create a complete example that demonstrates how to create an ExecutorService, submit tasks, and shut it down.

Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Task implements Runnable {
    private final String name;

    public Task(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("Task " + name + " is being executed by " + Thread.currentThread().getName());
        try {
            Thread.sleep(2000); // Simulate long-running task
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ExecutorServiceDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 1; i <= 5; i++) {
            Task task = new Task("Task " + i);
            executorService.submit(task);
        }

        executorService.shutdown();

        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }

        System.out.println("All tasks are finished.");
    }
}

Output:

Task Task 1 is being executed by pool-1-thread-1
Task Task 2 is being executed by pool-1-thread-2
Task Task 3 is being executed by pool-1-thread-3
Task Task 4 is being executed by pool-1-thread-1
Task Task 5 is being executed by pool-1-thread-2
All tasks are finished.

Explanation:

  • A fixed thread pool with 3 threads is created.
  • Five tasks are submitted to the executor service.
  • Each task prints its name and the name of the thread executing it.
  • The executor service is shut down gracefully, waiting for up to 60 seconds for tasks to complete.

6. Handling Callable and Future with ExecutorService

You can use the Callable interface with ExecutorService to handle tasks that return results and may throw exceptions.

Example:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class CallableTask implements Callable<String> {
    private final String name;

    public CallableTask(String name) {
        this.name = name;
    }

    @Override
    public String call() throws Exception {
        System.out.println("Task " + name + " is being executed by " + Thread.currentThread().getName());
        Thread.sleep(2000); // Simulate long-running task
        return "Result of " + name;
    }
}

public class CallableFutureDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        Future<String> future1 = executorService.submit(new CallableTask("Task 1"));
        Future<String> future2 = executorService.submit(new CallableTask("Task 2"));

        try {
            System.out.println(future1.get());
            System.out.println(future2.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}

Output:

Task Task 1 is being executed by pool-1-thread-1
Task Task 2 is being executed by pool-1-thread-2
Result of Task 1
Result of Task 2

Explanation:

  • The CallableTask class implements the Callable interface and returns a result.
  • Two Callable tasks are submitted to the executor service.
  • The results of the tasks are retrieved using the Future objects.

7. Example: ScheduledExecutorService

The ScheduledExecutorService interface extends ExecutorService and provides methods to schedule tasks to run after a delay or to execute periodically.

Example:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

class ScheduledTask implements Runnable {
    private final String name;

    public

 ScheduledTask(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("Task " + name + " is being executed by " + Thread.currentThread().getName());
    }
}

public class ScheduledExecutorServiceDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

        scheduledExecutorService.schedule(new ScheduledTask("Task 1"), 5, TimeUnit.SECONDS);
        scheduledExecutorService.schedule(new ScheduledTask("Task 2"), 10, TimeUnit.SECONDS);

        scheduledExecutorService.shutdown();
    }
}

Output:

Task Task 1 is being executed by pool-1-thread-1
Task Task 2 is being executed by pool-1-thread-2

Explanation:

  • A scheduled thread pool with 2 threads is created.
  • Two tasks are scheduled to run after a delay of 5 and 10 seconds, respectively.
  • The tasks print their name and the name of the thread executing them.

8. Conclusion

The ExecutorService interface in Java provides a powerful framework for managing and executing asynchronous tasks. By using ExecutorService, you can simplify the handling of multithreading, improve resource management, and enhance the performance of your applications. This tutorial covered the basics of creating an ExecutorService, submitting tasks, handling Callable and Future, and using ScheduledExecutorService.

Happy coding!

Comments