The Java Executor Framework is a powerful and flexible framework for managing and executing threads in Java. It simplifies thread management by abstracting the low-level details of thread creation, scheduling, and execution. The framework is part of the java.util.concurrent
package and provides several interfaces and classes for thread pooling and task execution.
1. Key Components of the Executor Framework
The Executor Framework consists of the following key components:
- Executor: The root interface for executing tasks.
- ExecutorService: A sub-interface of
Executor
that provides methods for managing the lifecycle of threads and tasks. - ScheduledExecutorService: A sub-interface of
ExecutorService
that supports scheduling tasks for future or periodic execution. - ThreadPoolExecutor: A concrete implementation of
ExecutorService
that manages a pool of threads. - Executors: A utility class for creating thread pools and executor services.
2. Creating Thread Pools
The Executors
class provides factory methods for creating different types of thread pools:
a. Fixed Thread Pool
- Creates a thread pool with a fixed number of threads.
- Tasks are executed concurrently by the available threads.
Example: Fixed Thread Pool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task1 = () -> System.out.println("Task 1 executed by " + Thread.currentThread().getName());
Runnable task2 = () -> System.out.println("Task 2 executed by " + Thread.currentThread().getName());
Runnable task3 = () -> System.out.println("Task 3 executed by " + Thread.currentThread().getName());
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.shutdown(); // Shutdown the executor
}
}
b. Cached Thread Pool
- Creates a thread pool that creates new threads as needed but reuses previously constructed threads when they are available.
- Suitable for short-lived asynchronous tasks.
Example: Cached Thread Pool
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
Runnable task1 = () -> System.out.println("Task 1 executed by " + Thread.currentThread().getName());
Runnable task2 = () -> System.out.println("Task 2 executed by " + Thread.currentThread().getName());
Runnable task3 = () -> System.out.println("Task 3 executed by " + Thread.currentThread().getName());
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.shutdown(); // Shutdown the executor
}
}
c. Single Thread Executor
- Creates a thread pool with a single thread.
- Tasks are executed sequentially.
Example: Single Thread Executor
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Runnable task1 = () -> System.out.println("Task 1 executed by " + Thread.currentThread().getName());
Runnable task2 = () -> System.out.println("Task 2 executed by " + Thread.currentThread().getName());
Runnable task3 = () -> System.out.println("Task 3 executed by " + Thread.currentThread().getName());
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.shutdown(); // Shutdown the executor
}
}
d. Scheduled Thread Pool
- Creates a thread pool that can schedule tasks to run after a delay or periodically.
Example: Scheduled Thread Pool
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> System.out.println("Task executed by " + Thread.currentThread().getName());
// Schedule task to run after 2 seconds
executor.schedule(task, 2, TimeUnit.SECONDS);
// Schedule task to run every 3 seconds after an initial delay of 1 second
executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
// Shutdown the executor after 10 seconds
executor.schedule(() -> executor.shutdown(), 10, TimeUnit.SECONDS);
}
}
3. Submitting Tasks
The ExecutorService
provides methods for submitting tasks for execution:
submit(Runnable task)
: Submits a task for execution and returns aFuture
object.execute(Runnable task)
: Executes the task without returning a result.invokeAll(Collection<Callable<T>> tasks)
: Executes all tasks and returns a list ofFuture
objects.invokeAny(Collection<Callable<T>> tasks)
: Executes all tasks and returns the result of the first completed task.
Example: Submitting Tasks
import java.util.concurrent.*;
public class SubmitTasksExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
// Submit a Runnable task
Future<?> future1 = executor.submit(() -> System.out.println("Runnable Task"));
// Submit a Callable task
Future<String> future2 = executor.submit(() -> "Callable Task");
// Get the result of the Callable task
System.out.println("Callable Task Result: " + future2.get());
executor.shutdown(); // Shutdown the executor
}
}
4. Shutting Down Executors
shutdown()
: Initiates an orderly shutdown of the executor. Previously submitted tasks are executed, but no new tasks are accepted.shutdownNow()
: Attempts to stop all actively executing tasks and halts the processing of waiting tasks.
Example: Shutting Down Executors
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ShutdownExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task = () -> System.out.println("Task executed by " + Thread.currentThread().getName());
executor.submit(task);
executor.submit(task);
executor.shutdown(); // Shutdown the executor
// Attempting to submit a new task after shutdown will throw RejectedExecutionException
try {
executor.submit(task);
} catch (Exception e) {
System.out.println("Task rejected: " + e.getMessage());
}
}
}
5. Handling Task Results with Future
The Future
interface represents the result of an asynchronous computation. It provides methods to check if the computation is complete, retrieve the result, or cancel the task.
Example: Using Future
import java.util.concurrent.*;
public class FutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<String> task = () -> {
Thread.sleep(2000); // Simulate a long-running task
return "Task Result";
};
Future<String> future = executor.submit(task);
System.out.println("Task submitted");
// Wait for the result
String result = future.get();
System.out.println("Task Result: " + result);
executor.shutdown(); // Shutdown the executor
}
}
6. Best Practices
- Use thread pools to manage resources efficiently.
- Prefer
Callable
overRunnable
if you need to return a result. - Always shutdown the executor to release resources.
- Use
Future
to handle task results and exceptions.
By mastering the Java Executor Framework, you can efficiently manage and execute tasks in a multi-threaded environment!