Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Executor Framework in Java

1. Introduction

The Executor Framework in Java simplifies concurrent programming by providing a higher level of abstraction for managing thread execution. It decouples task submission from the mechanics of how each task will be run, including details like thread management.

2. Key Concepts

  • Executor: An interface that defines a mechanism for executing a runnable task.
  • ExecutorService: A subinterface of Executor, providing methods for managing and controlling the lifecycle of tasks.
  • Future: Represents the result of an asynchronous computation, allowing you to retrieve the result or handle exceptions.

3. Executor Service

The ExecutorService interface extends Executor and provides methods for managing lifecycle, such as:

  • submit(): Submits a Callable or Runnable task for execution and returns a Future.
  • invokeAll(): Executes a collection of tasks and returns a list of Future objects.
  • shutdown(): Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.

4. Executor Implementations

Java provides several implementations of ExecutorService:

  • FixedThreadPool: A thread pool with a fixed number of threads.
  • CachedThreadPool: A thread pool that creates new threads as needed and reuses previously constructed threads when they are available.
  • SingleThreadExecutor: An executor that uses a single worker thread to process tasks.

5. Code Examples

5.1 Fixed Thread Pool Example


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

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("Executing task: " + taskId);
            });
        }
        
        executor.shutdown();
    }
}
            

5.2 Using Callable and Future


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 CallableFutureExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        
        Future future = executor.submit(new Callable() {
            public Integer call() {
                return 123;
            }
        });
        
        try {
            System.out.println("Future result: " + future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}
            

6. Best Practices

Follow these best practices when using the Executor Framework:

  • Use a thread pool to manage resources efficiently.
  • Prefer using Callable tasks when the task needs to return results.
  • Always remember to shut down the ExecutorService to prevent resource leaks.

7. FAQ

What is the difference between Executor and ExecutorService?

Executor is a simple interface for executing tasks, while ExecutorService extends it and adds methods to manage the lifecycle of tasks and provides features such as task scheduling.

How do I handle exceptions in a Callable task?

Exceptions thrown in a Callable can be caught when calling Future.get(), which will rethrow the exception wrapped in an ExecutionException.

What happens if I don’t shut down an ExecutorService?

If you don't shut down an ExecutorService, the application may not terminate as the JVM will wait for the running threads to finish, leading to potential resource leaks.